Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ground items kotlin #372

Open
wants to merge 1 commit into
base: kotlin-experiments
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions game/plugin/entity/ground-item/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
plugin {
name = "ground_item"
packageName = "org.apollo.game.plugin.entity"
authors = ["Ryley"]
}
13 changes: 13 additions & 0 deletions game/plugin/entity/ground-item/src/ground_item_action.plugin.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import org.apollo.game.message.impl.InventoryItemMessage

on { InventoryItemMessage::class }
.where { option == 5 }
.then {
// This is just a stub, for now.
// Several other things need to be done here:
// - Items may only be dropped from your inventory
// - Items dropped must be removed from your inventory
// - Items must be checked to ensure they have a 'drop' option
val item = it.inventory.get(slot)
it.addGroundItem(item, it.position)
}
72 changes: 72 additions & 0 deletions game/plugin/entity/ground-item/src/ground_item_sync.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import org.apollo.game.GameConstants
import org.apollo.game.model.entity.GroundItem
import org.apollo.game.scheduling.ScheduledTask

/**
* A [ScheduledTask] that manages the globalization and expiration of [GroundItem]s.
*/
class GroundItemSynchronizationTask(private val groundItem: GroundItem) : ScheduledTask(DELAY, false) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be better done as a single task that polls GroundItem's from a queue every tick.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind explaining how that queue should work? I don't think to queue them makes much sense here as all this task is doing is keeping the ground items state in sync with the client at the appropriate time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So there's one GroundItemSyncTask at all times and new ones are just added to its queue, rather than creating a new task for each item. Downside of this is we have to implement what is essentially scheduling logic in the task itself

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kind of defeats the purpose of the task scheduler, does it not?

Copy link
Member

@Major- Major- Apr 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially, yes; that is the downside


companion object {

/**
* The delay between executions of this task, in pulses.
*/
const val DELAY = 1

/**
* The amount of time, in pulses, in which this [GroundItem] will be globally visible.
*/
const val TRADABLE_TIME_UNTIL_GLOBAL = 60000 / GameConstants.PULSE_DELAY

/**
* The amount of time, in pulses, in which this [GroundItem] will expire and be removed from the [World].
*/
const val UNTRADABLE_TIME_UNTIL_EXPIRE = 180000 / GameConstants.PULSE_DELAY

/**
* The amount of time, in pulses, in which this [GroundItem] will expire and be removed from the [World].
*/
const val TIME_UNTIL_EXPIRE = 180000 / GameConstants.PULSE_DELAY
}

/**
* The amount of game pulses this [ScheduledTask] has been alive.
*/
private var pulses = 0

override fun execute() {
val world = groundItem.world
val owner = world.playerRepository[groundItem.ownerIndex]
val untradable = false // TODO: item.getDefinition().isTradable();

if (!groundItem.isAvailable) {
stop()
return
}

// Untradable items never go global
if (untradable) {
if (pulses >= UNTRADABLE_TIME_UNTIL_EXPIRE) {
world.removeGroundItem(owner, groundItem)
stop()
}
return
}

if (groundItem.isGlobal) {
if (pulses >= TIME_UNTIL_EXPIRE) {
world.removeGroundItem(owner, groundItem)
stop()
}
} else {
if (pulses >= TRADABLE_TIME_UNTIL_GLOBAL) {
groundItem.globalize()
world.addGroundItem(owner, groundItem)
}
}

pulses++
}

}
44 changes: 44 additions & 0 deletions game/plugin/entity/ground-item/src/ground_items.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import org.apollo.game.message.impl.RemoveTileItemMessage
import org.apollo.game.message.impl.SendTileItemMessage
import org.apollo.game.model.Item
import org.apollo.game.model.Position
import org.apollo.game.model.World
import org.apollo.game.model.entity.GroundItem
import org.apollo.game.model.entity.Player

/**
* Spawns a new local [GroundItem] for this Player at the specified [Position].
*/
fun Player.addGroundItem(item: Item, position: Position) {
world.addGroundItem(this, GroundItem.dropped(world, position, item, this))
}

internal fun World.addGroundItem(player: Player, item: GroundItem) {
val region = regionRepository.fromPosition(item.position)

if (item.isGlobal) {
region.addEntity(item, true)
return
}

groundItems.computeIfAbsent(player.encodedName, { HashSet<GroundItem>() }) += item

val offset = region.getPositionOffset(item)
player.send(SendTileItemMessage(item.item, offset))

schedule(GroundItemSynchronizationTask(item))
}

internal fun World.removeGroundItem(player: Player, item: GroundItem) {
val region = regionRepository.fromPosition(item.position)

if (item.isGlobal) {
region.removeEntity(item)
}

val items = groundItems[player.encodedName] ?: return
items -= item

val offset = region.getPositionOffset(item)
player.send(RemoveTileItemMessage(item.item, offset))
}
26 changes: 18 additions & 8 deletions game/src/main/java/org/apollo/game/model/World.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package org.apollo.game.model;

import java.util.*;
import java.util.logging.Logger;

import com.google.common.base.Preconditions;
import org.apollo.Service;
import org.apollo.cache.IndexedFileSystem;
Expand All @@ -19,11 +16,7 @@
import org.apollo.game.model.area.RegionRepository;
import org.apollo.game.model.area.collision.CollisionManager;
import org.apollo.game.model.area.collision.CollisionUpdateListener;
import org.apollo.game.model.entity.Entity;
import org.apollo.game.model.entity.EntityType;
import org.apollo.game.model.entity.MobRepository;
import org.apollo.game.model.entity.Npc;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.entity.*;
import org.apollo.game.model.event.Event;
import org.apollo.game.model.event.EventListener;
import org.apollo.game.model.event.EventListenerChainSet;
Expand All @@ -33,6 +26,9 @@
import org.apollo.game.scheduling.impl.NpcMovementTask;
import org.apollo.util.NameUtil;

import java.util.*;
import java.util.logging.Logger;

/**
* The world class is a singleton which contains objects like the {@link MobRepository} for players and NPCs. It should
* only contain things relevant to the in-game world and not classes which deal with I/O and such (these may be better
Expand Down Expand Up @@ -96,6 +92,11 @@ public enum RegistrationStatus {
*/
private final MobRepository<Player> playerRepository = new MobRepository<>(WorldConstants.MAXIMUM_PLAYERS);

/**
* A {@link Map} of player usernames to their local {@link Set} of {@link GroundItem}s.
*/
private final Map<Long, Set<GroundItem>> groundItems = new HashMap<>(WorldConstants.MAXIMUM_PLAYERS);

/**
* A {@link Map} of player usernames and the player objects.
*/
Expand Down Expand Up @@ -181,6 +182,15 @@ public MobRepository<Player> getPlayerRepository() {
return playerRepository;
}

/**
* Gets the {@link Map} of player usernames to their {@link Set} of {@link GroundItem}s
*
* @return The map.
*/
public Map<Long, Set<GroundItem>> getGroundItems() {
return groundItems;
}

/**
* Gets the plugin manager.
*
Expand Down
32 changes: 30 additions & 2 deletions game/src/main/java/org/apollo/game/model/area/Region.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.apollo.game.model.entity.Entity;
import org.apollo.game.model.entity.EntityType;
import org.apollo.game.model.entity.obj.DynamicGameObject;
import org.apollo.game.model.entity.obj.StaticGameObject;

import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -200,8 +201,8 @@ public boolean contains(Position position) {
*/
public Set<RegionUpdateMessage> encode(int height) {
Set<RegionUpdateMessage> additions = entities.values().stream()
.flatMap(Set::stream) // TODO fix this to work for ground items + projectiles
.filter(entity -> entity instanceof DynamicGameObject && entity.getPosition().getHeight() == height)
.flatMap(Set::stream) // TODO: Stop hurting my eyeballs.
.filter(entity -> !entity.getEntityType().isMob() && !(entity instanceof StaticGameObject) && (!(entity instanceof DynamicGameObject) || entity.getPosition().getHeight() == height))
.map(entity -> ((GroupableEntity) entity).toUpdateOperation(this, EntityUpdateType.ADD).toMessage())
.collect(Collectors.toSet());

Expand Down Expand Up @@ -269,6 +270,33 @@ public <T extends Entity> Set<T> getEntities(Position position, EntityType... ty
return ImmutableSet.copyOf(filtered);
}

/**
* Gets the position offset for the specified {@link Entity}.
*
* @param entity The Entity.
* @return The position offset.
*/
public int getPositionOffset(Entity entity) {
return getPositionOffset(entity.getPosition());
}

/**
* Gets the position offset for the specified {@link Position}.
*
* @param position The Entity.
* @return The position offset.
*/
public int getPositionOffset(Position position) {
RegionCoordinates coordinates = getCoordinates();
int dx = position.getX() - coordinates.getAbsoluteX();
int dy = position.getY() - coordinates.getAbsoluteY();

Preconditions.checkArgument(dx >= 0 && dx < Region.SIZE, position + " not in expected Region of " + toString() + ".");
Preconditions.checkArgument(dy >= 0 && dy < Region.SIZE, position + " not in expected Region of " + toString() + ".");

return dx << 4 | dy;
}

/**
* Gets the {@link Set} of {@link RegionCoordinates} of Regions that are viewable from the specified
* {@link Position}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public UpdateOperation(Region region, EntityUpdateType type, E entity) {
* @return The RegionUpdateMessage.
*/
public final RegionUpdateMessage inverse() {
int offset = getPositionOffset(entity.getPosition());
int offset = region.getPositionOffset(entity);

switch (type) {
case ADD:
Expand All @@ -71,7 +71,7 @@ public final RegionUpdateMessage inverse() {
* @return The Message.
*/
public final RegionUpdateMessage toMessage() {
int offset = getPositionOffset(entity.getPosition());
int offset = region.getPositionOffset(entity);

switch (type) {
case ADD:
Expand Down Expand Up @@ -99,21 +99,4 @@ public final RegionUpdateMessage toMessage() {
*/
protected abstract RegionUpdateMessage remove(int offset);

/**
* Gets the position offset for the specified {@link Position}.
*
* @param position The Position.
* @return The position offset.
*/
private final int getPositionOffset(Position position) {
RegionCoordinates coordinates = region.getCoordinates();
int dx = position.getX() - coordinates.getAbsoluteX();
int dy = position.getY() - coordinates.getAbsoluteY();

Preconditions.checkArgument(dx >= 0 && dx < Region.SIZE, position + " not in expected Region of " + region + ".");
Preconditions.checkArgument(dy >= 0 && dy < Region.SIZE, position + " not in expected Region of " + region + ".");

return dx << 4 | dy;
}

}
Loading