Repository reorganisation
Unified kotlin and java into just src/main Unified the rs09 and core packages Took all content out of the core package, and placed it into the new content package Reorganised all source code relating to content to be easier to find and explore
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
package core.game.activity;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.tools.SystemLogger;
|
||||
import core.game.world.GameWorld;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Manages the activities.
|
||||
* @author Emperor
|
||||
*/
|
||||
public final class ActivityManager {
|
||||
|
||||
/**
|
||||
* The mapping of instanced activities.
|
||||
*/
|
||||
private static final Map<String, ActivityPlugin> ACTIVITIES = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs a new {@code ActivityManager} {@code Object}.
|
||||
*/
|
||||
private ActivityManager() {
|
||||
/*
|
||||
* empty.
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an activity plugin.
|
||||
* @param plugin The plugin to register.
|
||||
*/
|
||||
public static void register(ActivityPlugin plugin) {
|
||||
plugin.register();
|
||||
ACTIVITIES.put(plugin.getName(), plugin);
|
||||
if (!plugin.isInstanced()) {
|
||||
plugin.configure();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an instanced activity.
|
||||
* @param player The player.
|
||||
* @param name The name.
|
||||
* @param login If we are logging in.
|
||||
* @param args The arguments.
|
||||
*/
|
||||
public static boolean start(Player player, String name, boolean login, Object... args) {
|
||||
ActivityPlugin plugin = ACTIVITIES.get(name);
|
||||
if (plugin == null) {
|
||||
if (GameWorld.getSettings().isDevMode()) {
|
||||
SystemLogger.logErr(ActivityManager.class, "Unhandled activity - " + name + "!");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (plugin.isInstanced()) {
|
||||
(plugin = plugin.newInstance(player)).configure();
|
||||
}
|
||||
return plugin.start(player, login, args);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
if (GameWorld.getSettings().isDevMode()) {
|
||||
player.getPacketDispatch().sendMessage("Error starting activity " + (plugin == null ? null : plugin.getName()) + "!");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the activity by the name.
|
||||
* @param name the name.
|
||||
* @return the activity.
|
||||
*/
|
||||
public static ActivityPlugin getActivity(String name) {
|
||||
return ACTIVITIES.get(name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
package core.game.activity;
|
||||
|
||||
import core.ServerConstants;
|
||||
import core.game.node.entity.Entity;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.world.map.Location;
|
||||
import core.game.world.map.Region;
|
||||
import core.game.world.map.build.DynamicRegion;
|
||||
import core.game.world.map.zone.*;
|
||||
import core.game.world.map.zone.impl.MultiwayCombatZone;
|
||||
import core.plugin.Plugin;
|
||||
import core.plugin.PluginManifest;
|
||||
import core.plugin.PluginType;
|
||||
|
||||
/**
|
||||
* A plugin implementation used for activity plugins.
|
||||
* @author Emperor
|
||||
*/
|
||||
@PluginManifest(type = PluginType.ACTIVITY)
|
||||
public abstract class ActivityPlugin extends MapZone implements Plugin<Player> {
|
||||
|
||||
/**
|
||||
* If the activity is instanced.
|
||||
*/
|
||||
private boolean instanced;
|
||||
|
||||
/**
|
||||
* If the activity is multicombat.
|
||||
*/
|
||||
private boolean multicombat;
|
||||
|
||||
/**
|
||||
* If the activity is safe.
|
||||
*/
|
||||
private boolean safe;
|
||||
|
||||
/**
|
||||
* The region of the activity.
|
||||
*/
|
||||
protected DynamicRegion region;
|
||||
|
||||
/**
|
||||
* The base location.
|
||||
*/
|
||||
protected Location base;
|
||||
|
||||
/**
|
||||
* A location which the player is teleported to in the case of death, if this activity is safe.
|
||||
* Defaults to the home location of the server.
|
||||
*/
|
||||
protected Location safeRespawn = ServerConstants.HOME_LOCATION;
|
||||
|
||||
/**
|
||||
* The player.
|
||||
*/
|
||||
protected Player player;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code ActivityPlugin} {@code Object}.
|
||||
* @param name The name.
|
||||
* @param instanced If the activity is instanced.
|
||||
* @param multicombat If the activity is multicombat.
|
||||
* @param safe If the activity is safe (the player does not lose his/her
|
||||
* items).
|
||||
*/
|
||||
public ActivityPlugin(String name, boolean instanced, boolean multicombat, boolean safe, ZoneRestriction... restrictions) {
|
||||
super(name, true, ZoneRestriction.RANDOM_EVENTS);
|
||||
for (ZoneRestriction restriction : restrictions) {
|
||||
addRestriction(restriction.getFlag());
|
||||
}
|
||||
this.instanced = instanced;
|
||||
this.multicombat = multicombat;
|
||||
this.safe = safe;
|
||||
if (safe) {
|
||||
setZoneType(ZoneType.SAFE.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(ZoneBorders borders) {
|
||||
if (multicombat) {
|
||||
MultiwayCombatZone.getInstance().register(borders);
|
||||
}
|
||||
super.register(borders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the region base location.
|
||||
*/
|
||||
protected void setRegionBase() {
|
||||
if (region != null) {
|
||||
if (multicombat) {
|
||||
region.toggleMulticombat();
|
||||
}
|
||||
setBase(Location.create(region.getBorders().getSouthWestX(), region.getBorders().getSouthWestY(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the region base for multiple regions.
|
||||
* @param regions The regions.
|
||||
*/
|
||||
protected void setRegionBase(DynamicRegion[] regions) {
|
||||
region = regions[0];
|
||||
Location l = region.getBaseLocation();
|
||||
for (DynamicRegion r : regions) {
|
||||
if (r.getX() > l.getX() || r.getY() > l.getY()) {
|
||||
l = r.getBaseLocation();
|
||||
}
|
||||
}
|
||||
ZoneBorders borders = new ZoneBorders(region.getX() << 6, region.getY() << 6, l.getX() + Region.SIZE, l.getY() + Region.SIZE);
|
||||
RegionZone multiZone = multicombat ? new RegionZone(MultiwayCombatZone.getInstance(), borders) : null;
|
||||
RegionZone zone = new RegionZone(this, borders);
|
||||
for (DynamicRegion r : regions) {
|
||||
if (multicombat) {
|
||||
r.setMulticombat(true);
|
||||
r.getRegionZones().add(multiZone);
|
||||
}
|
||||
r.getRegionZones().add(zone);
|
||||
}
|
||||
setBase(Location.create(borders.getSouthWestX(), borders.getSouthWestY(), 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the activity for the player.
|
||||
* @param player The player.
|
||||
* @param login If the player is logging in.
|
||||
* @param args The arguments.
|
||||
* @return {@code True} if successfully started the activity.
|
||||
*/
|
||||
public boolean start(Player player, boolean login, Object... args) {
|
||||
this.player = player;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enter(Entity e) {
|
||||
Location l;
|
||||
if (e instanceof Player && (l = getSpawnLocation()) != null) {
|
||||
e.getProperties().setSpawnLocation(l);
|
||||
}
|
||||
e.getProperties().setSafeZone(safe);
|
||||
e.getProperties().safeRespawn = this.safeRespawn;
|
||||
e.setAttribute("activity", this);
|
||||
return super.enter(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean leave(Entity e, boolean logout) {
|
||||
if (e instanceof Player) {
|
||||
e.getProperties().setSpawnLocation(ServerConstants.HOME_LOCATION);
|
||||
}
|
||||
Location l;
|
||||
if (instanced && logout && (l = getSpawnLocation()) != null) {
|
||||
e.setLocation(l);
|
||||
}
|
||||
e.getProperties().setSafeZone(false);
|
||||
e.getProperties().safeRespawn = ServerConstants.HOME_LOCATION;
|
||||
e.removeAttribute("activity");
|
||||
return super.leave(e, logout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to do anything on registration.
|
||||
*/
|
||||
public void register() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fireEvent(String identifier, Object... args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract ActivityPlugin newInstance(Player p) throws Throwable;
|
||||
|
||||
/**
|
||||
* Gets the spawn location for this activity.
|
||||
*/
|
||||
public abstract Location getSpawnLocation();
|
||||
|
||||
/**
|
||||
* Gets the instanced.
|
||||
* @return The instanced.
|
||||
*/
|
||||
public boolean isInstanced() {
|
||||
return instanced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the instanced.
|
||||
* @param instanced The instanced to set.
|
||||
*/
|
||||
public void setInstanced(boolean instanced) {
|
||||
this.instanced = instanced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the multicombat.
|
||||
* @return The multicombat.
|
||||
*/
|
||||
public boolean isMulticombat() {
|
||||
return multicombat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the multicombat.
|
||||
* @param multicombat The multicombat to set.
|
||||
*/
|
||||
public void setMulticombat(boolean multicombat) {
|
||||
this.multicombat = multicombat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the safe.
|
||||
* @return The safe.
|
||||
*/
|
||||
public boolean isSafe() {
|
||||
return safe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the safe.
|
||||
* @param safe The safe to set.
|
||||
*/
|
||||
public void setSafe(boolean safe) {
|
||||
this.safe = safe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player.
|
||||
* @return The player.
|
||||
*/
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base.
|
||||
* @return The base.
|
||||
*/
|
||||
public Location getBase() {
|
||||
return base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the base.
|
||||
* @param base The base to set.
|
||||
*/
|
||||
public void setBase(Location base) {
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
package core.game.activity
|
||||
|
||||
import core.api.*
|
||||
import core.game.event.EventHook
|
||||
import core.game.event.SelfDeathEvent
|
||||
import core.game.component.Component
|
||||
import core.game.interaction.MovementPulse
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.node.entity.npc.NPC
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.scenery.Scenery
|
||||
import core.game.system.task.Pulse
|
||||
import core.game.world.map.Direction
|
||||
import core.game.world.map.Location
|
||||
import core.game.world.map.Region
|
||||
import core.game.world.map.RegionManager
|
||||
import core.game.world.map.build.DynamicRegion
|
||||
import core.game.world.map.path.Pathfinder
|
||||
import core.net.packet.PacketRepository
|
||||
import core.net.packet.context.MinimapStateContext
|
||||
import core.net.packet.out.MinimapState
|
||||
import org.rs09.consts.Components
|
||||
import core.ServerConstants
|
||||
import core.api.Event
|
||||
import core.api.utils.PlayerCamera
|
||||
import core.tools.SystemLogger
|
||||
import core.game.world.GameWorld
|
||||
|
||||
/**
|
||||
* A utility class for making cutscenes.
|
||||
* @author Ceikry
|
||||
*/
|
||||
abstract class Cutscene(val player: Player) {
|
||||
lateinit var region: Region
|
||||
lateinit var base: Location
|
||||
var exitLocation: Location = player.location.transform(0,0,0)
|
||||
var ended = false
|
||||
|
||||
val camera = PlayerCamera(player)
|
||||
private val addedNPCs = HashMap<Int, ArrayList<NPC>>()
|
||||
|
||||
abstract fun setup()
|
||||
abstract fun runStage(stage: Int)
|
||||
|
||||
/**
|
||||
* Creates a new dynamic copy of the region identified by regionId, sets this cutscene's region as this new copy, and clears any cutscene-spawned NPCs from the previous region.
|
||||
* @param regionId the region ID to duplicate.
|
||||
*/
|
||||
fun loadRegion(regionId: Int)
|
||||
{
|
||||
clearNPCs()
|
||||
logCutscene("Creating new instance of region $regionId for ${player.username}.")
|
||||
region = DynamicRegion.create(regionId)
|
||||
logCutscene("Dynamic region instantiated for ${player.username}. Global coordinates: ${region.baseLocation}.")
|
||||
base = region.baseLocation
|
||||
}
|
||||
|
||||
/**
|
||||
* Fade the player's view to black. This process can be safely assumed to take about 8 ticks to complete.
|
||||
*/
|
||||
fun fadeToBlack()
|
||||
{
|
||||
logCutscene("Fading ${player.username}'s screen to black.")
|
||||
player.interfaceManager.closeOverlay()
|
||||
player.interfaceManager.openOverlay(Component(Components.FADE_TO_BLACK_120))
|
||||
}
|
||||
|
||||
/**
|
||||
* Fade the player's view from black back to normal. This process can be safely assumed to take about 6 ticks to complete.
|
||||
*/
|
||||
fun fadeFromBlack()
|
||||
{
|
||||
logCutscene("Fading ${player.username}'s screen from black to normal.")
|
||||
player.interfaceManager.closeOverlay()
|
||||
player.interfaceManager.openOverlay(Component(Components.FADE_FROM_BLACK_170))
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleports an entity to a pair of coordinates within the currently active cutscene region
|
||||
* @param entity the entity to teleport
|
||||
* @param regionX the region X coordinate to teleport the entity to (0-63)
|
||||
* @param regionY the region Y coordinate to teleport the entity to (0-63)
|
||||
* @param plane (optional) the plane to teleport to (0-3)
|
||||
*/
|
||||
fun teleport(entity: Entity, regionX: Int, regionY: Int, plane: Int = 0)
|
||||
{
|
||||
val newLoc = base.transform(regionX, regionY, plane)
|
||||
logCutscene("Teleporting ${entity.username} to coordinates: LOCAL[$regionX,$regionY,$plane] GLOBAL[${newLoc.x},${newLoc.y},$plane].")
|
||||
entity.properties.teleportLocation = newLoc
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves an entity to the given coordinates within the currently active cutscene region
|
||||
* @param entity the entity to move
|
||||
* @param regionX the region X coordinate to move the entity to (0-63)
|
||||
* @param regionY the region Y coordinate to move the entity to (0-63)
|
||||
*/
|
||||
fun move(entity: Entity, regionX: Int, regionY: Int)
|
||||
{
|
||||
logCutscene("Moving ${entity.username} to LOCAL[$regionX,$regionY].")
|
||||
entity.pulseManager.run(object : MovementPulse(entity, base.transform(regionX,regionY,0), Pathfinder.SMART) {
|
||||
override fun pulse(): Boolean {
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a dialogue to the player using the given NPC ID, which updates the cutscene stage by default when continued.
|
||||
* @param npcId the ID of the NPC to send a dialogue for
|
||||
* @param expression the FacialExpression the NPC should use
|
||||
* @param message the message to send
|
||||
* @param onContinue (optional) a method that runs when the dialogue is "continued." Increments the cutscene stage by default.
|
||||
*/
|
||||
fun dialogueUpdate(npcId: Int, expression: core.game.dialogue.FacialExpression, message: String, onContinue: () -> Unit = {incrementStage()})
|
||||
{
|
||||
logCutscene("Sending NPC dialogue update.")
|
||||
sendNPCDialogue(player, npcId, message, expression)
|
||||
player.dialogueInterpreter.addAction { _,_ -> onContinue.invoke() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a non-NPC dialogue to the player, which updates the cutscene stage by default when continued
|
||||
* @param message the message to send
|
||||
* @param onContinue (optional) a method that runs when the dialogue is "continued." Increments the cutscene stage by default.
|
||||
*/
|
||||
fun dialogueUpdate(message: String, onContinue: () -> Unit = {incrementStage()})
|
||||
{
|
||||
logCutscene("Sending standard dialogue update.")
|
||||
sendDialogue(player, message)
|
||||
player.dialogueInterpreter.addAction {_,_ -> onContinue.invoke()}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a player dialogue, which updates the cutscene stage by default when continued
|
||||
* @param expression the FacialExpression to use
|
||||
* @param message the message to send
|
||||
* @param onContinue (optional) a method that runs when the dialogue is "continued." Increments the cutscene stage by default.
|
||||
*/
|
||||
fun playerDialogueUpdate(expression: core.game.dialogue.FacialExpression, message: String, onContinue: () -> Unit = {incrementStage()})
|
||||
{
|
||||
logCutscene("Sending player dialogue update")
|
||||
sendPlayerDialogue(player, message, expression)
|
||||
player.dialogueInterpreter.addAction{_,_ -> onContinue.invoke()}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the stage after a set number of ticks.
|
||||
* @param ticks the number of ticks to wait
|
||||
* @param newStage (optional) the new stage to update to. If not passed, stage is incremented instead.
|
||||
*/
|
||||
fun timedUpdate(ticks: Int, newStage: Int = -1)
|
||||
{
|
||||
logCutscene("Executing timed updated for $ticks ticks.")
|
||||
GameWorld.Pulser.submit(object : Pulse(ticks)
|
||||
{
|
||||
override fun pulse(): Boolean {
|
||||
if(newStage == -1)
|
||||
incrementStage()
|
||||
else
|
||||
updateStage(newStage)
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the first NPC from the added-to-cutscene NPCs with a matching ID.
|
||||
* @param id the ID to grab for.
|
||||
* @return the first NPC, or null if there are no matching NPCs.
|
||||
*/
|
||||
fun getNPC(id: Int): NPC?
|
||||
{
|
||||
return addedNPCs[id]?.firstOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all NPCs from the added-to-cutscene NPCs with a matching ID.
|
||||
* @param id the ID to grab for.
|
||||
* @return an ArrayList of the matching NPCs, which will be empty if there were no matches.
|
||||
*/
|
||||
fun getNPCs(id: Int): ArrayList<NPC>
|
||||
{
|
||||
return addedNPCs[id] ?: ArrayList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the object in the region at the given regionX and regionY
|
||||
* @param regionX the region-local X coordinate to grab the object from (0-63)
|
||||
* @param regionY the region-local Y coordinate to grab the object from (0-63)
|
||||
* @param plane the plane to grab the object from (0-3)
|
||||
*/
|
||||
fun getObject(regionX: Int, regionY: Int, plane: Int = 0): Scenery?
|
||||
{
|
||||
val obj = RegionManager.getObject(base.transform(regionX,regionY,plane))
|
||||
logCutscene("Retrieving object at LOCAL[$regionX,$regionY], GOT: ${obj?.definition?.name ?: "null"}.")
|
||||
return obj
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an NPC to the cutscene with the given ID, at the region-local X and Y coordinates.
|
||||
* @param id the ID of the NPC to add.
|
||||
* @param regionX the region-local X coordinate to place the NPC at
|
||||
* @param regionY the region-local Y coordinate to place the NPC at
|
||||
* @param direction the direction the NPC faces when it is initially placed.
|
||||
* @param plane the plane to place te NPC on. Default is 0.
|
||||
*/
|
||||
fun addNPC(id: Int, regionX: Int, regionY: Int, direction: Direction, plane: Int = 0)
|
||||
{
|
||||
val npc = NPC(id)
|
||||
npc.isRespawn = false
|
||||
npc.isAggressive = false
|
||||
npc.isWalks = false
|
||||
npc.location = base.transform(regionX, regionY, plane)
|
||||
npc.init()
|
||||
npc.faceLocation(npc.location.transform(direction))
|
||||
|
||||
val npcs = addedNPCs[id] ?: ArrayList()
|
||||
npcs.add(npc)
|
||||
addedNPCs[id] = npcs
|
||||
logCutscene("Added NPC $id at location LOCAL[$regionX,$regionY,$plane] GLOBAL[${npc.location.x},${npc.location.y},$plane]")
|
||||
}
|
||||
|
||||
fun start()
|
||||
{
|
||||
logCutscene("Starting cutscene for ${player.username}.")
|
||||
region = RegionManager.forId(player.location.regionId)
|
||||
base = RegionManager.forId(player.location.regionId).baseLocation
|
||||
setup()
|
||||
PacketRepository.send(MinimapState::class.java, MinimapStateContext(player, 2))
|
||||
runStage(player.getCutsceneStage())
|
||||
setAttribute(player, ATTRIBUTE_CUTSCENE, this)
|
||||
setAttribute(player, ATTRIBUTE_CUTSCENE_STAGE, 0)
|
||||
player.interfaceManager.removeTabs(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14)
|
||||
player.properties.isSafeZone = true
|
||||
player.properties.safeRespawn = player.location
|
||||
player.lock()
|
||||
player.hook(Event.SelfDeath, CUTSCENE_DEATH_HOOK)
|
||||
player.logoutListeners["cutscene"] = {player -> player.location = exitLocation; player.getCutscene()?.end() }
|
||||
content.global.ame.RandomEventManager.getInstance(player)!!.enabled = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends this cutscene, fading the screen to black, teleporting the player to the exit location, and then fading it back in and executing the endActions passed to this method.
|
||||
* @param endActions (optional) a method that executes when the cutscene fully completes
|
||||
*/
|
||||
fun end(endActions: (() -> Unit)? = null)
|
||||
{
|
||||
ended = true
|
||||
fadeToBlack()
|
||||
GameWorld.Pulser.submit(object : Pulse(){
|
||||
var tick: Int = 0
|
||||
override fun pulse(): Boolean {
|
||||
when(tick++)
|
||||
{
|
||||
8 -> player.properties.teleportLocation = exitLocation
|
||||
9 -> fadeFromBlack()
|
||||
16 -> {
|
||||
try {
|
||||
endActions?.invoke()
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.logErr(this::class.java, "There's some bad nasty code in ${this::class.java.simpleName} end actions!")
|
||||
e.printStackTrace()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
super.stop()
|
||||
player ?: return
|
||||
player.removeAttribute(ATTRIBUTE_CUTSCENE)
|
||||
player.removeAttribute(ATTRIBUTE_CUTSCENE_STAGE)
|
||||
player.properties.isSafeZone = false
|
||||
player.properties.safeRespawn = ServerConstants.HOME_LOCATION
|
||||
player.interfaceManager.restoreTabs()
|
||||
player.unlock()
|
||||
clearNPCs()
|
||||
player.unhook(CUTSCENE_DEATH_HOOK)
|
||||
player.logoutListeners.remove("cutscene")
|
||||
content.global.ame.RandomEventManager.getInstance(player)?.enabled = true
|
||||
PacketRepository.send(MinimapState::class.java, MinimapStateContext(player, 0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the camera to the specified regionX and regionY
|
||||
* @param regionX the region-local X coordinate to move the camera to (0-63)
|
||||
* @param regionY the region-local Y coordinate to move the camera to (0-63)
|
||||
* @param height (optional) the height of the camera, defaults to 300.
|
||||
* @param speed (optional) the speed of the camera transition, defaults to 100.
|
||||
*/
|
||||
fun moveCamera(regionX: Int, regionY: Int, height: Int = 300, speed: Int = 100)
|
||||
{
|
||||
val globalLoc = base.transform(regionX, regionY, 0)
|
||||
camera.panTo(globalLoc.x, globalLoc.y, height, speed)
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the camera to face the given region-local X and Y coordinates
|
||||
* @param regionX the region-local X coordinate to rotate the camera to (0-63)
|
||||
* @param regionY the region-local Y coordinate to rotate the camera to (0-63)
|
||||
* @param height (optional) the height of the camera, defaults to 300.
|
||||
* @param speed (optional) the speed of the camera transition, defaults to 100.
|
||||
*/
|
||||
fun rotateCamera(regionX: Int, regionY: Int, height: Int = 300, speed: Int = 100)
|
||||
{
|
||||
val globalLoc = base.transform(regionX, regionY, 0)
|
||||
camera.rotateTo(globalLoc.x, globalLoc.y, height, speed)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the location the player is placed at when the cutscene ends.
|
||||
*/
|
||||
fun setExit(location: Location)
|
||||
{
|
||||
exitLocation = location
|
||||
}
|
||||
|
||||
private fun loadCurrentStage()
|
||||
{
|
||||
if(ended) return
|
||||
runStage(player.getCutsceneStage())
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the player's stage attribute by 1
|
||||
*/
|
||||
fun incrementStage()
|
||||
{
|
||||
setAttribute(player, ATTRIBUTE_CUTSCENE_STAGE, player.getCutsceneStage() + 1)
|
||||
loadCurrentStage()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the player's stage attribute to a new stage
|
||||
*/
|
||||
fun updateStage(newStage: Int)
|
||||
{
|
||||
setAttribute(player, ATTRIBUTE_CUTSCENE_STAGE, newStage)
|
||||
loadCurrentStage()
|
||||
}
|
||||
|
||||
fun logCutscene(message: String)
|
||||
{
|
||||
if(ServerConstants.LOG_CUTSCENE)
|
||||
SystemLogger.logInfo(this::class.java, "$message")
|
||||
}
|
||||
|
||||
fun clearNPCs() {
|
||||
for(entry in addedNPCs.entries)
|
||||
{
|
||||
logCutscene("Clearing ${entry.value.size} NPCs with ID ${entry.key} for ${player.username}.")
|
||||
for(npc in entry.value) npc.clear()
|
||||
}
|
||||
addedNPCs.clear()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ATTRIBUTE_CUTSCENE = "cutscene"
|
||||
const val ATTRIBUTE_CUTSCENE_STAGE = "cutscene:stage"
|
||||
object CUTSCENE_DEATH_HOOK : EventHook<SelfDeathEvent>
|
||||
{
|
||||
override fun process(entity: Entity, event: SelfDeathEvent) {
|
||||
if(entity !is Player) return
|
||||
entity.getCutscene()?.end() ?: entity.unhook(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
package core.game.activity;
|
||||
|
||||
import core.game.component.Component;
|
||||
import core.game.node.entity.Entity;
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.system.task.Pulse;
|
||||
import core.game.world.GameWorld;
|
||||
import core.game.world.map.Location;
|
||||
import core.game.world.map.build.DynamicRegion;
|
||||
import core.net.packet.PacketRepository;
|
||||
import core.net.packet.context.MinimapStateContext;
|
||||
import core.net.packet.out.MinimapState;
|
||||
import core.plugin.PluginManifest;
|
||||
import core.plugin.PluginType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents the plugin used to handle a cutscene.
|
||||
* @author Vexia
|
||||
* @date 28/12/2013
|
||||
*/
|
||||
@PluginManifest(type = PluginType.ACTIVITY)
|
||||
public abstract class CutscenePlugin extends ActivityPlugin {
|
||||
|
||||
/**
|
||||
* The list of tabs to remove.
|
||||
*/
|
||||
private static final int[] TABS = new int[] { 0, 1, 2, 3, 4, 5, 6, 11, 12 };
|
||||
|
||||
/**
|
||||
* The npcs in our cutscene.
|
||||
*/
|
||||
protected final List<NPC> npcs = new ArrayList<>(100);
|
||||
|
||||
/**
|
||||
* The start pulse used for effect.
|
||||
*/
|
||||
private final StartPulse startPulse = new StartPulse();
|
||||
|
||||
/**
|
||||
* The ending pulse used for effect.
|
||||
*/
|
||||
private final EndPulse endPulse = new EndPulse();
|
||||
|
||||
/**
|
||||
* If we should use a fade in or not.
|
||||
*/
|
||||
private final boolean fade;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code CutscenePlugin} {@code Object}.
|
||||
* @param name the name of the cutscene/mapzone.
|
||||
* @param fade fading in or not.
|
||||
*/
|
||||
public CutscenePlugin(String name, final boolean fade) {
|
||||
super(name, true, false, true);
|
||||
this.fade = fade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code CutscenePlugin} {@code Object}.
|
||||
* @param name the name.
|
||||
*/
|
||||
public CutscenePlugin(final String name) {
|
||||
this(name, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start(final Player player, boolean login, Object... args) {
|
||||
player.setAttribute("cutscene:original-loc", player.getLocation());
|
||||
player.removeAttribute("real-end");
|
||||
player.setAttribute("real-end", player.getLocation());
|
||||
if (isFade()) {
|
||||
GameWorld.getPulser().submit(getStartPulse());
|
||||
} else {
|
||||
PacketRepository.send(MinimapState.class, new MinimapStateContext(player, getMapState()));
|
||||
player.getInterfaceManager().removeTabs(getRemovedTabs());
|
||||
player.getProperties().setTeleportLocation(getStartLocation());
|
||||
player.unlock();
|
||||
player.getWalkingQueue().reset();
|
||||
player.getLocks().lockMovement(1000000);
|
||||
player.getInterfaceManager().close();
|
||||
open();
|
||||
}
|
||||
player.lock();
|
||||
return super.start(player, login, args);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public boolean leave(final Entity e, boolean logout) {
|
||||
if (player != null) {
|
||||
if (logout) {
|
||||
player.setLocation(player.getAttribute("cutscene:original-loc", player.getLocation()));
|
||||
end();
|
||||
} else {
|
||||
unpause();
|
||||
}
|
||||
player.unlock();
|
||||
player.getInterfaceManager().openDefaultTabs();
|
||||
player.getWalkingQueue().reset();
|
||||
player.getLocks().unlockMovement();
|
||||
}
|
||||
return super.leave(e, logout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fireEvent(String identifier, Object... args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to stop the cutscene.
|
||||
* @param fade if we should use a fade cutout.
|
||||
*/
|
||||
public void stop(boolean fade) {
|
||||
if (fade) {
|
||||
GameWorld.getPulser().submit(endPulse);
|
||||
} else {
|
||||
end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to end the cutscene.
|
||||
*/
|
||||
public void end() {
|
||||
if (region != null) {
|
||||
for (int i = 0; i < region.getPlanes().length; i++) {
|
||||
for (NPC n : region.getPlanes()[i].getNpcs()) {
|
||||
if (n == null) {
|
||||
continue;
|
||||
}
|
||||
n.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
PacketRepository.send(MinimapState.class, new MinimapStateContext(player, 0));
|
||||
player.getInterfaceManager().restoreTabs();
|
||||
player.unlock();// incase he was locked.
|
||||
player.getWalkingQueue().reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the pulse used when starting a cutscene. This is used to give
|
||||
* dramatic effect for entering a cutscene. In the future allow for this to
|
||||
* be toggled.
|
||||
* @author 'Vexia
|
||||
* @date 30/12/2013
|
||||
*/
|
||||
public class StartPulse extends Pulse {
|
||||
|
||||
/**
|
||||
* Represents the counter.
|
||||
*/
|
||||
private int counter = 0;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code StartPulse} {@code Object}.
|
||||
*/
|
||||
public StartPulse() {
|
||||
super(1, player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
switch (counter++) {
|
||||
case 1:
|
||||
player.lock();
|
||||
player.getInterfaceManager().openOverlay(new Component(115));
|
||||
break;
|
||||
case 3:
|
||||
PacketRepository.send(MinimapState.class, new MinimapStateContext(player, getMapState()));
|
||||
player.getInterfaceManager().removeTabs(getRemovedTabs());
|
||||
break;
|
||||
case 4:
|
||||
player.getProperties().setTeleportLocation(getStartLocation());
|
||||
break;
|
||||
case 5:
|
||||
player.getInterfaceManager().closeOverlay();
|
||||
player.getInterfaceManager().close();
|
||||
player.unlock();
|
||||
player.getWalkingQueue().reset();
|
||||
player.getLocks().lockMovement(1000000);
|
||||
open();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the pulse used when ending a cutscene. This is used to give
|
||||
* dramatic effect for entering a cutscene.
|
||||
* @author 'Vexia
|
||||
* @date 30/12/2013
|
||||
*/
|
||||
public class EndPulse extends Pulse {
|
||||
|
||||
/**
|
||||
* Represents the counter.
|
||||
*/
|
||||
private int counter = 0;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code EndPulse} {@code Object}.
|
||||
*/
|
||||
public EndPulse() {
|
||||
super(1, player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
switch (counter++) {
|
||||
case 1:
|
||||
player.lock();
|
||||
player.getInterfaceManager().openOverlay(new Component(115));
|
||||
break;
|
||||
case 3:
|
||||
PacketRepository.send(MinimapState.class, new MinimapStateContext(player, getMapState()));
|
||||
player.getInterfaceManager().removeTabs(getRemovedTabs());
|
||||
break;
|
||||
case 4:
|
||||
Location loc = (Location) (player.getAttribute("real-end", player.getAttribute("cutscene:original-loc", player.getLocation())));
|
||||
player.getProperties().setTeleportLocation(loc);
|
||||
break;
|
||||
case 5:
|
||||
end();
|
||||
stop();
|
||||
fade();// specfic for fadeout.
|
||||
if (player.getSession().isActive()) {
|
||||
PacketRepository.send(MinimapState.class, new MinimapStateContext(player, 0));
|
||||
}
|
||||
player.getInterfaceManager().closeOverlay();
|
||||
if (player.getSession().isActive()) {
|
||||
player.getInterfaceManager().close();
|
||||
}
|
||||
player.getProperties().setSafeZone(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called when the dim interface is closed. And you can see the
|
||||
* cutscene.
|
||||
*/
|
||||
public void open() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called on the end of the cutscene.
|
||||
*/
|
||||
public void fade() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mapstate to use. (override if needed).
|
||||
* @return the state.
|
||||
*/
|
||||
public int getMapState() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the removed tabs. (override if needed).
|
||||
* @return the tabs.
|
||||
*/
|
||||
public int[] getRemovedTabs() {
|
||||
return TABS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the starting location (region based, override if needed).
|
||||
* @return the location.
|
||||
*/
|
||||
public Location getStartLocation() {
|
||||
return getBase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to unpause this cutscene.
|
||||
*/
|
||||
public final void unpause() {
|
||||
stop(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player.
|
||||
* @return The player.
|
||||
*/
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the region.
|
||||
* @return The region.
|
||||
*/
|
||||
public DynamicRegion getRegion() {
|
||||
return region;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nPCS.
|
||||
* @return The nPCS.
|
||||
*/
|
||||
public List<NPC> getNPCS() {
|
||||
return npcs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fade.
|
||||
* @return The fade.
|
||||
*/
|
||||
public boolean isFade() {
|
||||
return fade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the start pulse.
|
||||
* @return the pulse.
|
||||
*/
|
||||
public Pulse getStartPulse() {
|
||||
return startPulse;
|
||||
}
|
||||
|
||||
public Pulse getEndPulse(){return new EndPulse();}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,578 @@
|
||||
package core.game.bots;
|
||||
|
||||
import core.game.node.entity.impl.PulseType;
|
||||
import core.ServerConstants;
|
||||
import core.game.container.impl.EquipmentContainer;
|
||||
import core.game.interaction.DestinationFlag;
|
||||
import core.game.interaction.MovementPulse;
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.Entity;
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.entity.player.info.PlayerDetails;
|
||||
import core.game.node.entity.player.link.appearance.Gender;
|
||||
import core.game.node.item.Item;
|
||||
import core.game.world.map.Direction;
|
||||
import core.game.world.map.Location;
|
||||
import core.game.world.map.RegionManager;
|
||||
import core.game.world.map.path.Pathfinder;
|
||||
import core.game.world.map.zone.impl.WildernessZone;
|
||||
import core.game.world.repository.Repository;
|
||||
import core.net.packet.context.MessageContext;
|
||||
import core.tools.RandomFunction;
|
||||
import core.tools.StringUtils;
|
||||
import core.game.node.entity.skill.Skills;
|
||||
import content.region.misc.tutisland.handlers.iface.CharacterDesign;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Represents an <b>A</b>rtificial <b>I</b>ntelligent <b>P</b>layer.
|
||||
*
|
||||
* @author Emperor
|
||||
*/
|
||||
public class AIPlayer extends Player {
|
||||
|
||||
/**
|
||||
* The current UID.
|
||||
*/
|
||||
private static int currentUID = 0x1;
|
||||
|
||||
private static final List<String> botNames = new ArrayList<String>();
|
||||
|
||||
/**
|
||||
* The active Artificial intelligent players mapping.
|
||||
*/
|
||||
private static final Map<Integer, AIPlayer> botMapping = new HashMap<>();
|
||||
|
||||
/**
|
||||
* A line of data from namesandarmor.txt that will be used to generate the appearance
|
||||
* Data in format:
|
||||
* //name:cblevel:helmet:cape:neck:weapon:chest:shield:unknown:legs:unknown:gloves:boots:
|
||||
*/
|
||||
private static String OSRScopyLine;
|
||||
|
||||
/**
|
||||
* The AIP's UID.
|
||||
*/
|
||||
private final int uid;
|
||||
|
||||
/**
|
||||
* The start location of the AIP.
|
||||
*/
|
||||
private final Location startLocation;
|
||||
|
||||
/**
|
||||
* The username.
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* The player controlling this AIP.
|
||||
*/
|
||||
private Player controler;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new {@code AIPlayer} {@code Object}.
|
||||
*
|
||||
* @param l The location.
|
||||
*/
|
||||
static {
|
||||
loadNames("botnames.txt");
|
||||
}
|
||||
|
||||
public AIPlayer(Location l) {
|
||||
this(getRandomName(), l, null);
|
||||
}
|
||||
|
||||
public AIPlayer(String fileName, Location l) {
|
||||
this(retrieveRandomName(fileName), l, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private AIPlayer(String name, Location l, String ignored) {
|
||||
super(new PlayerDetails("/aip" + (currentUID + 1) + ":" + name));
|
||||
super.setLocation(startLocation = l);
|
||||
super.artificial = true;
|
||||
super.getDetails().setSession(ArtificialSession.getSingleton());
|
||||
Repository.getPlayers().add(this);
|
||||
this.username = StringUtils.formatDisplayName(name + (currentUID + 1));
|
||||
this.uid = currentUID++;
|
||||
this.updateRandomValues();
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates bot stats/equipment/etc based on OSRScopyLine
|
||||
*/
|
||||
public void updateRandomValues() {
|
||||
this.getAppearance().setGender(RandomFunction.random(5) == 1 ? Gender.FEMALE : Gender.MALE);
|
||||
int setTo = RandomFunction.random(0,10);
|
||||
CharacterDesign.randomize(this,true);
|
||||
this.setDirection(Direction.values()[new Random().nextInt(Direction.values().length)]); //Random facing dir
|
||||
this.getSkills().updateCombatLevel();
|
||||
this.getAppearance().sync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
return;
|
||||
}
|
||||
|
||||
private void setLevels() {
|
||||
//Create realistic player stats
|
||||
int maxLevel = RandomFunction.random(1, Math.min(parseOSRS(1), 99));
|
||||
for (int i = 0; i < Skills.NUM_SKILLS; i++) {
|
||||
this.getSkills().setStaticLevel(i, RandomFunction.linearDecreaseRand(maxLevel));
|
||||
}
|
||||
int combatLevelsLeft = parseOSRS(1);
|
||||
int hitpoints = Math.max(RandomFunction.random(10, Math.min(maxLevel, combatLevelsLeft * 4)), 10);
|
||||
combatLevelsLeft -= 0.25 * hitpoints;
|
||||
int prayer = combatLevelsLeft > 0 ? RandomFunction.random(Math.min(maxLevel, combatLevelsLeft * 8)) : 1;
|
||||
combatLevelsLeft -= 0.125 * prayer;
|
||||
int defence = combatLevelsLeft > 0 ? RandomFunction.random(Math.min(maxLevel, combatLevelsLeft * 4)) : 1;
|
||||
combatLevelsLeft -= 0.25 * defence;
|
||||
|
||||
combatLevelsLeft = Math.min(combatLevelsLeft, 199);
|
||||
|
||||
int attack = combatLevelsLeft > 0 ? RandomFunction.normalRandDist(Math.min(maxLevel, combatLevelsLeft * 3)) : 1;
|
||||
int strength = combatLevelsLeft > 0 ? combatLevelsLeft * 3 - attack : 1;
|
||||
|
||||
this.getSkills().setStaticLevel(Skills.HITPOINTS, hitpoints);
|
||||
this.getSkills().setStaticLevel(Skills.PRAYER, prayer);
|
||||
this.getSkills().setStaticLevel(Skills.DEFENCE, defence);
|
||||
this.getSkills().setStaticLevel(Skills.ATTACK, attack);
|
||||
this.getSkills().setStaticLevel(Skills.STRENGTH, strength);
|
||||
this.getSkills().setStaticLevel(Skills.RANGE, combatLevelsLeft / 2);
|
||||
this.getSkills().setStaticLevel(Skills.MAGIC, combatLevelsLeft / 2);
|
||||
}
|
||||
|
||||
private void giveArmor() {
|
||||
//name:cblevel:helmet2:cape3:neck4:weapon5:chest6:shield7:unknown8:legs9:unknown10:gloves11:boots12:
|
||||
//sicriona:103:1163: 1023: 1725 :1333: 1127 :1201 :0: 1079 :0: 2922: 1061:0:
|
||||
equipIfExists(new Item(parseOSRS(2)), EquipmentContainer.SLOT_HAT);
|
||||
equipIfExists(new Item(parseOSRS(3)), EquipmentContainer.SLOT_CAPE);
|
||||
equipIfExists(new Item(parseOSRS(4)), EquipmentContainer.SLOT_AMULET);
|
||||
equipIfExists(new Item(parseOSRS(5)), EquipmentContainer.SLOT_WEAPON);
|
||||
equipIfExists(new Item(parseOSRS(6)), EquipmentContainer.SLOT_CHEST);
|
||||
equipIfExists(new Item(parseOSRS(7)), EquipmentContainer.SLOT_SHIELD);
|
||||
equipIfExists(new Item(parseOSRS(9)), EquipmentContainer.SLOT_LEGS);
|
||||
equipIfExists(new Item(parseOSRS(11)), EquipmentContainer.SLOT_HANDS);
|
||||
equipIfExists(new Item(parseOSRS(12)), EquipmentContainer.SLOT_FEET);
|
||||
}
|
||||
|
||||
private int parseOSRS(int index) {
|
||||
return Integer.parseInt(OSRScopyLine.split(":")[index]);
|
||||
}
|
||||
|
||||
private void equipIfExists(Item e, int slot) {
|
||||
if (e == null || e.getName().equalsIgnoreCase("null")) {
|
||||
return;
|
||||
}
|
||||
if (e.getId() != 0)
|
||||
getEquipment().replace(e, slot);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a list of bot names into memory
|
||||
*/
|
||||
public static void loadNames(String fileName){
|
||||
try {
|
||||
Scanner sc = new Scanner(new File(ServerConstants.BOT_DATA_PATH + fileName));
|
||||
while (sc.hasNextLine()) {
|
||||
botNames.add(sc.nextLine());
|
||||
}
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getRandomName(){
|
||||
int index = (RandomFunction.random(botNames.size()));
|
||||
String name = botNames.get(index);
|
||||
botNames.remove(index);
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a bot content
|
||||
*/
|
||||
public static void updateRandomOSRScopyLine(String fileName) {
|
||||
Random rand = new Random();
|
||||
int n = 0;
|
||||
try {
|
||||
for (Scanner sc = new Scanner(new File(ServerConstants.BOT_DATA_PATH + fileName)); sc.hasNext(); ) {
|
||||
++n;
|
||||
String line = sc.nextLine();
|
||||
if (rand.nextInt(n) == 0) { //Chance of overwriting line is lower and lower
|
||||
if (line.length() < 3 || line.startsWith("#")) //probably an empty line
|
||||
{
|
||||
continue;
|
||||
}
|
||||
OSRScopyLine = line;
|
||||
}
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
System.out.println("Missing " + fileName);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static String retrieveRandomName(String fileName) {
|
||||
do {
|
||||
updateRandomOSRScopyLine(fileName);
|
||||
} while (OSRScopyLine.startsWith("#") || OSRScopyLine.contains("_") || OSRScopyLine.contains(" ")); //Comment
|
||||
return OSRScopyLine.split(":")[0];
|
||||
}
|
||||
|
||||
private static String retrieveRandomName() {
|
||||
return retrieveRandomName("namesandarmor.txt");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
getProperties().setSpawnLocation(startLocation);
|
||||
getInterfaceManager().openDefaultTabs();
|
||||
getSession().setObject(this);
|
||||
botMapping.put(uid, this);
|
||||
super.init();
|
||||
getSettings().setRunToggled(true);
|
||||
CharacterDesign.randomize(this, false);
|
||||
getPlayerFlags().setLastSceneGraph(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the following.
|
||||
*
|
||||
* @param e The entity to follow.
|
||||
*/
|
||||
public void follow(final Entity e) {
|
||||
getPulseManager().run(new MovementPulse(this, e, DestinationFlag.FOLLOW_ENTITY) {
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
face(e);
|
||||
return false;
|
||||
}
|
||||
}, PulseType.STANDARD);
|
||||
}
|
||||
|
||||
public void randomWalkAroundPoint(Location point, int radius) {
|
||||
Pathfinder.find(this, point.transform(RandomFunction.random(radius, (radius * -1)), RandomFunction.random(radius, (radius * -1)), 0), true, Pathfinder.SMART).walk(this);
|
||||
}
|
||||
|
||||
public void randomWalk(int radiusX, int radiusY) {
|
||||
Pathfinder.find(this, this.getLocation().transform(RandomFunction.random(radiusX, (radiusX * -1)), RandomFunction.random(radiusY, (radiusY * -1)), 0), false, Pathfinder.SMART).walk(this);
|
||||
}
|
||||
|
||||
public void walkToPosSmart(int x, int y) {
|
||||
walkToPosSmart(new Location(x, y));
|
||||
}
|
||||
|
||||
public void walkToPosSmart(Location loc) {
|
||||
Pathfinder.find(this, loc, true, Pathfinder.SMART).walk(this);
|
||||
}
|
||||
|
||||
public void walkPos(int x, int y) {
|
||||
Pathfinder.find(this, new Location(x, y));
|
||||
}
|
||||
|
||||
public boolean checkVictimIsPlayer() {
|
||||
if (this.getProperties().getCombatPulse().getVictim() != null)
|
||||
if (this.getProperties().getCombatPulse().getVictim().isPlayer())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
if(getSkullManager().isWilderness()) {
|
||||
getSkullManager().setLevel(WildernessZone.getWilderness(this));
|
||||
}
|
||||
if(getSkills().getLifepoints() <= 0){
|
||||
//deregister(this.uid);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public Item getItemById(int id) {
|
||||
for (int i = 0; i < 28; i++) {
|
||||
Item item = this.getInventory().get(i);
|
||||
if (item != null) {
|
||||
if (item.getId() == id)
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void handleIncomingChat(MessageContext ctx){
|
||||
}
|
||||
|
||||
|
||||
private ArrayList<Node> getNodeInRange(int range, int entry) {
|
||||
int meX = this.getLocation().getX();
|
||||
int meY = this.getLocation().getY();
|
||||
ArrayList<Node> nodes = new ArrayList<Node>();
|
||||
for (NPC npc : RegionManager.getLocalNpcs(this, range)) {
|
||||
if (npc.getId() == entry)
|
||||
nodes.add(npc);
|
||||
}
|
||||
for (int x = 0; x < range; x++) {
|
||||
for (int y = 0; y < range - x; y++) {
|
||||
Node node = RegionManager.getObject(0, meX + x, meY + y);
|
||||
if (node != null)
|
||||
if (node.getId() == entry)
|
||||
nodes.add(node);
|
||||
Node node2 = RegionManager.getObject(0, meX + x, meY - y);
|
||||
if (node2 != null)
|
||||
if (node2.getId() == entry)
|
||||
nodes.add(node2);
|
||||
Node node3 = RegionManager.getObject(0, meX - x, meY + y);
|
||||
if (node3 != null)
|
||||
if (node3.getId() == entry)
|
||||
nodes.add(node3);
|
||||
Node node4 = RegionManager.getObject(0, meX - x, meY - y);
|
||||
if (node4 != null)
|
||||
if (node4.getId() == entry)
|
||||
nodes.add(node4);
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
private ArrayList<Node> getNodeInRange(int range, List<Integer> entrys) {
|
||||
int meX = this.getLocation().getX();
|
||||
int meY = this.getLocation().getY();
|
||||
//int meX2 = this.getLocation().getX();
|
||||
//System.out.println("local " + meX + " real x? " + meX2 );
|
||||
ArrayList<Node> nodes = new ArrayList<Node>();
|
||||
for (NPC npc : RegionManager.getLocalNpcs(this, range)) {
|
||||
if (entrys.contains(npc.getId()))
|
||||
nodes.add(npc);
|
||||
}
|
||||
for (int x = 0; x < range; x++) {
|
||||
for (int y = 0; y < range - x; y++) {
|
||||
Node node = RegionManager.getObject(0, meX + x, meY + y);
|
||||
if (node != null)
|
||||
if (entrys.contains(node.getId()))
|
||||
nodes.add(node);
|
||||
Node node2 = RegionManager.getObject(0, meX + x, meY - y);
|
||||
if (node2 != null)
|
||||
if (entrys.contains(node2.getId()))
|
||||
nodes.add(node2);
|
||||
Node node3 = RegionManager.getObject(0, meX - x, meY + y);
|
||||
if (node3 != null)
|
||||
if (entrys.contains(node3.getId()))
|
||||
nodes.add(node3);
|
||||
Node node4 = RegionManager.getObject(0, meX - x, meY - y);
|
||||
if (node4 != null)
|
||||
if (entrys.contains(node4.getId()))
|
||||
nodes.add(node4);
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public Node getClosestNodeWithEntryAndDirection(int range, int entry, Direction direction) {
|
||||
ArrayList<Node> nodeList = getNodeInRange(range, entry);
|
||||
if (nodeList.isEmpty()) {
|
||||
//System.out.println("nodelist empty");
|
||||
return null;
|
||||
}
|
||||
Node node = getClosestNodeinNodeListWithDirection(nodeList, direction);
|
||||
return node;
|
||||
}
|
||||
|
||||
public Node getClosestNodeWithEntry(int range, int entry) {
|
||||
ArrayList<Node> nodeList = getNodeInRange(range, entry);
|
||||
if (nodeList.isEmpty()) {
|
||||
//System.out.println("nodelist empty");
|
||||
return null;
|
||||
}
|
||||
Node node = getClosestNodeinNodeList(nodeList);
|
||||
return node;
|
||||
}
|
||||
|
||||
public Node getClosestNodeWithEntry(int range, List<Integer> entrys) {
|
||||
ArrayList<Node> nodeList = getNodeInRange(range, entrys);
|
||||
if (nodeList.isEmpty()) {
|
||||
//System.out.println("nodelist empty");
|
||||
return null;
|
||||
}
|
||||
Node node = getClosestNodeinNodeList(nodeList);
|
||||
return node;
|
||||
}
|
||||
|
||||
public Node getClosesCreature(int radius) {
|
||||
int distance = radius + 1;
|
||||
Node npcReturn = null;
|
||||
for (NPC npc : RegionManager.getLocalNpcs(this, radius)) {
|
||||
double distanceToNpc = npc.getLocation().getDistance(this.getLocation());
|
||||
if ((distanceToNpc) < distance) {
|
||||
distance = (int) distanceToNpc;
|
||||
npcReturn = npc;
|
||||
}
|
||||
}
|
||||
return npcReturn;
|
||||
}
|
||||
|
||||
public Node getClosesCreature(int radius, int entry) {
|
||||
int distance = radius + 1;
|
||||
Node npcReturn = null;
|
||||
for (NPC npc : RegionManager.getLocalNpcs(this, radius)) {
|
||||
double distanceToNpc = npc.getLocation().getDistance(this.getLocation());
|
||||
if (npc.getId() == entry) {
|
||||
if ((distanceToNpc) < distance) {
|
||||
distance = (int) distanceToNpc;
|
||||
npcReturn = npc;
|
||||
}
|
||||
}
|
||||
}
|
||||
return npcReturn;
|
||||
}
|
||||
|
||||
public Node getClosesCreature(int radius, ArrayList<Integer> entrys) {
|
||||
int distance = radius + 1;
|
||||
Node npcReturn = null;
|
||||
for (NPC npc : RegionManager.getLocalNpcs(this, radius)) {
|
||||
double distanceToNpc = npc.getLocation().getDistance(this.getLocation());
|
||||
if (entrys.contains(npc.getId())) {
|
||||
if ((distanceToNpc) < distance) {
|
||||
distance = (int) distanceToNpc;
|
||||
npcReturn = npc;
|
||||
}
|
||||
}
|
||||
}
|
||||
return npcReturn;
|
||||
}
|
||||
|
||||
private Node getClosestNodeinNodeListWithDirection(ArrayList<Node> nodes, Direction direction) {
|
||||
if (nodes.isEmpty()) {
|
||||
//System.out.println("nodelist empty");
|
||||
return null;
|
||||
}
|
||||
|
||||
double distance = 0;
|
||||
Node nodeReturn = null;
|
||||
for (Node node : nodes) {
|
||||
double nodeDistance = this.getLocation().getDistance(node.getLocation());
|
||||
if ((nodeReturn == null || nodeDistance < distance) && node.getDirection() == direction) {
|
||||
distance = nodeDistance;
|
||||
nodeReturn = node;
|
||||
}
|
||||
}
|
||||
return nodeReturn;
|
||||
}
|
||||
|
||||
private Node getClosestNodeinNodeList(ArrayList<Node> nodes) {
|
||||
if (nodes.isEmpty()) {
|
||||
//System.out.println("nodelist empty");
|
||||
return null;
|
||||
}
|
||||
|
||||
double distance = 0;
|
||||
Node nodeReturn = null;
|
||||
for (Node node : nodes) {
|
||||
double nodeDistance = this.getLocation().getDistance(node.getLocation());
|
||||
if (nodeReturn == null || nodeDistance < distance) {
|
||||
distance = nodeDistance;
|
||||
nodeReturn = node;
|
||||
}
|
||||
}
|
||||
return nodeReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
botMapping.remove(uid);
|
||||
super.clear(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
if (getPlayerFlags().isUpdateSceneGraph()) {
|
||||
getPlayerFlags().setLastSceneGraph(getLocation());
|
||||
}
|
||||
super.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finalizeDeath(Entity killer) {
|
||||
super.finalizeDeath(killer);
|
||||
fullRestore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the UID.
|
||||
*
|
||||
* @return the UID.
|
||||
*/
|
||||
public int getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deregisters an AIP.
|
||||
*
|
||||
* @param uid The player's UID.
|
||||
*/
|
||||
public static void deregister(int uid) {
|
||||
AIPlayer player = botMapping.get(uid);
|
||||
if (player != null) {
|
||||
player.clear();
|
||||
Repository.getPlayers().remove(player);
|
||||
return;
|
||||
}
|
||||
//SystemLogger.logErr(this.getClass(), "Could not deregister AIP#" + uid + ": UID not added to the mapping!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the AIP for the given UID.
|
||||
*
|
||||
* @param uid The UID.
|
||||
* @return The AIPlayer.
|
||||
*/
|
||||
public static AIPlayer get(int uid) {
|
||||
return botMapping.get(uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the startLocation.
|
||||
*/
|
||||
public Location getStartLocation() {
|
||||
return startLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the controler.
|
||||
*
|
||||
* @return The controler.
|
||||
*/
|
||||
public Player getControler() {
|
||||
return controler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the controler.
|
||||
*
|
||||
* @param controler The controler to set.
|
||||
*/
|
||||
public void setControler(Player controler) {
|
||||
this.controler = controler;
|
||||
}
|
||||
|
||||
|
||||
public void interact(Node n) {
|
||||
// InteractionPacket.handleObjectInteraction(this, 0, n.getLocation(), n.getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package core.game.bots
|
||||
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.item.GroundItem
|
||||
import core.game.ge.GrandExchangeOffer
|
||||
|
||||
/**
|
||||
* A repository for bots to make use of that can contain any info that might be useful to them.
|
||||
* @author Ceikry
|
||||
*/
|
||||
class AIRepository {
|
||||
companion object {
|
||||
val groundItems = HashMap<Player,ArrayList<GroundItem>>()
|
||||
val GEOffers = HashMap<Player, GrandExchangeOffer>()
|
||||
|
||||
@JvmStatic
|
||||
val PulseRepository = HashMap<String, GeneralBotCreator.BotScriptPulse>() // Lowercase username, pulse
|
||||
|
||||
@JvmStatic
|
||||
fun addItem(item: GroundItem){
|
||||
if(groundItems[item.dropper] == null){
|
||||
val list = ArrayList<GroundItem>()
|
||||
list.add(item)
|
||||
groundItems[item.dropper] = list
|
||||
return
|
||||
}
|
||||
groundItems[item.dropper]!!.add(item)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getItems(player: Player): ArrayList<GroundItem>?{
|
||||
return groundItems[player]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addOffer(player: Player, offer: GrandExchangeOffer){
|
||||
GEOffers[player] = offer
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getOffer(player: Player): GrandExchangeOffer? {
|
||||
return GEOffers[player]
|
||||
}
|
||||
|
||||
@JvmStatic fun clearAllBots() {
|
||||
PulseRepository.forEach { (_, it) ->
|
||||
it.stop();
|
||||
it.botScript.bot.clear();
|
||||
AIPlayer.deregister((it.botScript.bot as AIPlayer).uid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package core.game.bots;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import core.net.IoSession;
|
||||
|
||||
/**
|
||||
* Represents an artificial networking session.
|
||||
* @author Emperor
|
||||
*/
|
||||
public final class ArtificialSession extends IoSession {
|
||||
|
||||
/**
|
||||
* The artificial session singleton.
|
||||
*/
|
||||
private static final ArtificialSession SINGLETON = new ArtificialSession();
|
||||
|
||||
/**
|
||||
* Constructs a new {@code ArtificialSession} {@code Object}.
|
||||
*/
|
||||
private ArtificialSession() {
|
||||
super(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteAddress() {
|
||||
return "127.0.0.1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object context, boolean instant) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queue(ByteBuffer buffer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the singleton.
|
||||
*/
|
||||
public static ArtificialSession getSingleton() {
|
||||
return SINGLETON;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package core.game.bots
|
||||
|
||||
import core.game.node.entity.player.link.appearance.Gender
|
||||
import core.game.node.entity.player.link.prayer.PrayerType
|
||||
import core.game.node.item.Item
|
||||
import core.game.world.map.Direction
|
||||
import core.game.world.map.Location
|
||||
import core.tools.RandomFunction
|
||||
import core.game.consumable.Consumable
|
||||
import content.data.consumables.Consumables
|
||||
import core.game.consumable.Food
|
||||
import content.data.consumables.effects.HealingEffect
|
||||
import core.game.node.entity.skill.Skills
|
||||
import java.util.*
|
||||
|
||||
class CombatBot(location: Location) : AIPlayer(location) {
|
||||
var tick = 0
|
||||
|
||||
override fun updateRandomValues() {
|
||||
appearance.gender = if (RandomFunction.random(5) == 1) Gender.FEMALE else Gender.MALE
|
||||
setDirection(Direction.values()[Random().nextInt(Direction.values().size)]) //Random facing dir
|
||||
skills.updateCombatLevel()
|
||||
appearance.sync()
|
||||
}
|
||||
override fun tick() {
|
||||
super.tick()
|
||||
this.tick++
|
||||
|
||||
//Despawn
|
||||
if (skills.lifepoints == 0) {
|
||||
//CombatBotAssembler.produce(CombatBotAssembler.Type.RANGE, CombatBotAssembler.Tier.LOW,this.location)
|
||||
deregister(uid)
|
||||
}
|
||||
}
|
||||
|
||||
fun CheckPrayer(type: Array<PrayerType?>) {
|
||||
for (i in type.indices) prayer.toggle(type[i])
|
||||
}
|
||||
|
||||
fun eat(foodId: Int) {
|
||||
val foodItem = Item(foodId)
|
||||
if (skills.getStaticLevel(Skills.HITPOINTS) >= skills.lifepoints * 3 && inventory.containsItem(foodItem)) {
|
||||
this.lock(3)
|
||||
//this.animate(new Animation(829));
|
||||
val food = inventory.getItem(foodItem)
|
||||
var consumable: Consumable? = Consumables.getConsumableById(food.id)
|
||||
if (consumable == null) {
|
||||
consumable = Food(IntArray(food.id), HealingEffect(1))
|
||||
}
|
||||
consumable.consume(food, this)
|
||||
properties.combatPulse.delayNextAttack(3)
|
||||
}
|
||||
if (!checkVictimIsPlayer()) if (!inventory.contains(foodId, 1)) inventory.add(Item(foodId, 5)) //Add Food to Inventory
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,532 @@
|
||||
package core.game.bots
|
||||
|
||||
import core.game.node.entity.skill.Skills
|
||||
import core.game.node.item.Item
|
||||
import core.game.world.map.Location
|
||||
import core.tools.RandomFunction
|
||||
import org.rs09.consts.Items
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
/**
|
||||
* Assembles combat-style bots, intended to replace PVMBotBuilder.
|
||||
* @author Ceikry
|
||||
*/
|
||||
class CombatBotAssembler {
|
||||
enum class Type{
|
||||
RANGE,
|
||||
MAGE,
|
||||
MELEE
|
||||
}
|
||||
enum class Tier{
|
||||
LOW,
|
||||
MED,
|
||||
HIGH,
|
||||
PURE
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a bot with the specified parameters.
|
||||
* @param type the type of bot to produce. Melee, Ranged, or Mage.
|
||||
* @param tier specifies the range of skills the bot can have, and thereby, the gear. LOW, MED, or HIGH.
|
||||
* @param location the location that the bot spawns and respawns at.
|
||||
* @return an AIPlayer with the specified values.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun produce(type: Type, tier: Tier, location: Location): AIPlayer? {
|
||||
return when (type) {
|
||||
Type.RANGE -> assembleRangedBot(tier, location)
|
||||
Type.MELEE -> assembleMeleeBot(tier,location)
|
||||
Type.MAGE -> assembleMeleeBot(tier,location)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles a ranged bot with the specified values.
|
||||
* @param tier the tier to use which defines skill ranges and gear.
|
||||
* @param location the bot's spawn and respawn location
|
||||
* @param crossbow (optional) whether or not this bot should use a crossbow
|
||||
* @return a CombatBot with the specified values.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun assembleRangedBot(tier: Tier, location: Location, crossbow: Boolean? = null): CombatBot {
|
||||
val bot = CombatBot(location)
|
||||
|
||||
generateStats(bot, tier, Skills.RANGE, Skills.DEFENCE)
|
||||
gearRangedBot(bot, crossbow ?: Random().nextInt() % 2 == 0)
|
||||
return bot
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles a melee bot with the specified values.
|
||||
* @param tier the tier of the bot. Specifies levels and gear.
|
||||
* @param location the spawn and respawn location of the bot.
|
||||
* @return a CombatBot with the specified values.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun assembleMeleeBot(tier: Tier, location: Location): CombatBot {
|
||||
val bot = CombatBot(location)
|
||||
|
||||
generateStats(bot, tier, Skills.ATTACK, Skills.STRENGTH, Skills.DEFENCE)
|
||||
gearMeleeBot(bot)
|
||||
return bot
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles a melee bot with gear appropriate for killing dragons.
|
||||
* @param tier the tier of the bot, specifying levels and gear.
|
||||
* @param location the spawn and respawn location of the bot.
|
||||
* @return a CombatBot suited for killing dragons.
|
||||
* @author Kermit
|
||||
*/
|
||||
fun MeleeAdventurer(tier: Tier, location: Location): CombatBot {
|
||||
val bot = CombatBot(location)
|
||||
var max = 0
|
||||
val level = RandomFunction.random(25, 65).also {max = 99 }
|
||||
generateStats(bot,tier,Skills.ATTACK, Skills.STRENGTH, Skills.DEFENCE)
|
||||
bot.skills.setStaticLevel(Skills.HITPOINTS, level)
|
||||
bot.skills.setStaticLevel(Skills.ATTACK, level + 5)
|
||||
bot.skills.setStaticLevel(Skills.STRENGTH, level + 5)
|
||||
bot.skills.setLevel(Skills.HITPOINTS, level)
|
||||
bot.skills.setLevel(Skills.ATTACK, level + 5)
|
||||
bot.skills.setLevel(Skills.STRENGTH, level + 5)
|
||||
bot.skills.updateCombatLevel()
|
||||
equipHighest(bot, MELEE_HELMS)
|
||||
equipHighest(bot, MELEE_TOP)
|
||||
equipHighest(bot, MELEE_LEG)
|
||||
equipHighest(bot, MELEE_WEP)
|
||||
equipHighest(bot, MELEE_SHIELD)
|
||||
equipHighest(bot, CAPE)
|
||||
equipHighest(bot, NNECK)
|
||||
equipHighest(bot, NGLOVES)
|
||||
equipHighest(bot, NBOOTS)
|
||||
bot.equipment.refresh()
|
||||
return bot
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles a melee bot with gear appropriate for killing dragons.
|
||||
* @param tier the tier of the bot, specifying levels and gear.
|
||||
* @param location the spawn and respawn location of the bot.
|
||||
* @return a CombatBot suited for killing dragons.
|
||||
* @author Kermit
|
||||
*/
|
||||
fun RangeAdventurer(tier: Tier, location: Location): CombatBot {
|
||||
val bot = CombatBot(location)
|
||||
var max = 0
|
||||
val level = RandomFunction.random(35, 65).also {max = 75 }
|
||||
generateStats(bot,tier,Skills.ATTACK, Skills.STRENGTH)
|
||||
bot.skills.setStaticLevel(Skills.HITPOINTS, level)
|
||||
bot.skills.setStaticLevel(Skills.DEFENCE, level)
|
||||
bot.skills.setStaticLevel(Skills.RANGE, level + 10)
|
||||
bot.skills.setLevel(Skills.HITPOINTS, level)
|
||||
bot.skills.setLevel(Skills.DEFENCE, level)
|
||||
bot.skills.setLevel(Skills.RANGE, level + 10)
|
||||
bot.skills.updateCombatLevel()
|
||||
equipHighest(bot,RANGE_HELMS,65)
|
||||
equipHighest(bot,RANGE_TOPS,65)
|
||||
equipHighest(bot,RANGE_LEGS,65)
|
||||
equipHighest(bot,CROSSBOWS,50)
|
||||
equipHighest(bot, CAPE)
|
||||
equipHighest(bot, NRANGENECK)
|
||||
equipHighest(bot, NRANGESHIELD)
|
||||
equipHighest(bot, PCRANGE_BACK)
|
||||
equipHighest(bot, NGLOVES)
|
||||
equipHighest(bot, NRBOOTS)
|
||||
bot.equipment.add(Item(Items.BRONZE_BOLTS_877,100000),13,false,false)
|
||||
bot.equipment.refresh()
|
||||
return bot
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles a melee bot with gear appropriate for killing dragons.
|
||||
* @param tier the tier of the bot, specifying levels and gear.
|
||||
* @param location the spawn and respawn location of the bot.
|
||||
* @return a CombatBot suited for killing dragons.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun assembleMeleeDragonBot(tier: Tier, location: Location): CombatBot {
|
||||
val bot = CombatBot(location)
|
||||
generateStats(bot,tier,Skills.ATTACK, Skills.STRENGTH, Skills.DEFENCE)
|
||||
equipHighest(bot, MELEE_HELMS, 50)
|
||||
equipHighest(bot, MELEE_TOP, 40)
|
||||
equipHighest(bot, MELEE_LEG, 40)
|
||||
equipHighest(bot, MELEE_WEP, 60)
|
||||
equipHighest(bot, CAPE)
|
||||
equipHighest(bot, NGLOVES)
|
||||
equipHighest(bot, NBOOTS)
|
||||
bot.equipment.refresh()
|
||||
return bot
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles a ranged bot with gear appropriate for killing dragons.
|
||||
* @param tier the tier of the bot, specifying levels and gear.
|
||||
* @param location the spawn and respawn location of the bot.
|
||||
* @return a CombatBot suited for killing dragons.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun assembleRangeDragonBot(tier: Tier, location: Location): CombatBot {
|
||||
val bot = CombatBot(location)
|
||||
bot.fullRestore()
|
||||
generateStats(bot,tier,Skills.RANGE,Skills.DEFENCE)
|
||||
equipHighest(bot,RANGE_HELMS,50)
|
||||
equipHighest(bot,RANGE_TOPS,50)
|
||||
equipHighest(bot,RANGE_LEGS,50)
|
||||
equipHighest(bot,CROSSBOWS,50)
|
||||
equipHighest(bot, CAPE)
|
||||
equipHighest(bot, NGLOVES)
|
||||
equipHighest(bot, NRBOOTS)
|
||||
bot.equipment.add(Item(Items.BRONZE_BOLTS_877,100000),13,false,false)
|
||||
bot.equipment.refresh()
|
||||
return bot
|
||||
}
|
||||
|
||||
/**
|
||||
* Gears a ranged bot with the best gear for its stats.
|
||||
* @param bot the bot to gear
|
||||
* @param crossbow (optional) whether or not this bot should use a crossbow. Default is false.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun gearRangedBot(bot: AIPlayer, crossbow: Boolean? = false) {
|
||||
equipHighest(bot, RANGE_HELMS)
|
||||
equipHighest(bot, RANGE_TOPS)
|
||||
equipHighest(bot, RANGE_LEGS)
|
||||
equipHighest(bot, CAPE)
|
||||
equipHighest(bot, NGLOVES)
|
||||
equipHighest(bot, NRBOOTS)
|
||||
if(crossbow == true) { equipHighest(bot,CROSSBOWS); equipHighest(bot,MELEE_SHIELD); bot.equipment.add(Item(Items.BRONZE_BOLTS_877,Integer.MAX_VALUE),13,false,false) }
|
||||
else {equipHighest(bot, BOWS); bot.equipment.add(Item(Items.BRONZE_ARROW_882,Integer.MAX_VALUE),13,false,false) }
|
||||
bot.equipment.refresh()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gears a melee bot with the best gear for its stats.
|
||||
* @param bot the bot to gear.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun gearMeleeBot(bot: AIPlayer){
|
||||
equipHighest(bot, MELEE_HELMS)
|
||||
equipHighest(bot, MELEE_LEG)
|
||||
equipHighest(bot, MELEE_SHIELD)
|
||||
equipHighest(bot, MELEE_TOP)
|
||||
equipHighest(bot, CAPE)
|
||||
equipHighest(bot, NGLOVES)
|
||||
equipHighest(bot, NBOOTS)
|
||||
equipHighest(bot, MELEE_WEP)
|
||||
bot.equipment.refresh()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gears a Novice Pest control bot.
|
||||
* @author Sir Kermit & Ceikry
|
||||
*/
|
||||
fun gearPCnRangedBot(bot: AIPlayer, crossbow: Boolean? = false, vararg skills: Int) {
|
||||
var max = 0
|
||||
val level = RandomFunction.random(30, 70).also {max = 75 }
|
||||
bot.fullRestore()
|
||||
|
||||
bot.skills.setStaticLevel(Skills.RANGE, 50)
|
||||
bot.skills.setStaticLevel(Skills.DEFENCE, 50)
|
||||
bot.skills.setStaticLevel(Skills.ATTACK, level)
|
||||
bot.skills.setStaticLevel(Skills.STRENGTH, level)
|
||||
bot.skills.setStaticLevel(Skills.HITPOINTS, level)
|
||||
bot.skills.setLevel(Skills.RANGE, 50)
|
||||
bot.skills.setLevel(Skills.DEFENCE, 50)
|
||||
bot.skills.setLevel(Skills.ATTACK, level)
|
||||
bot.skills.setLevel(Skills.STRENGTH, level)
|
||||
bot.skills.setLevel(Skills.HITPOINTS, level)
|
||||
bot.skills.updateCombatLevel()
|
||||
equipHighest(bot, PCRANGE_HELMS)
|
||||
equipHighest(bot, PCRANGE_TOPS)
|
||||
equipHighest(bot, PCRANGE_BACK)
|
||||
equipHighest(bot, PCRANGE_LEGS)
|
||||
bot.equipment.refresh()
|
||||
equipHighest(bot, NECK)
|
||||
equipHighest(bot, GLOVES)
|
||||
equipHighest(bot, BOOTS)
|
||||
equipHighest(bot, RING_ARCH)
|
||||
bot.equipment.refresh()
|
||||
if(crossbow == true) { equipHighest(bot,PCCROSSBOWS); equipHighest(bot,MELEE_SHIELD); bot.equipment.add(Item(Items.BRONZE_BOLTS_877,Integer.MAX_VALUE),13,false,false) }
|
||||
else {equipHighest(bot, PCBOWS); bot.equipment.add(Item(Items.BRONZE_ARROW_882,Integer.MAX_VALUE),13,false,false) }
|
||||
bot.skills.setStaticLevel(Skills.RANGE, 99)
|
||||
bot.skills.setLevel(Skills.RANGE, 99)
|
||||
bot.equipment.refresh()
|
||||
}
|
||||
|
||||
fun gearPCnMeleeBot(bot: AIPlayer, vararg skills: Int){
|
||||
var max = 0
|
||||
val initial = RandomFunction.random(30, 75).also {max = 75 }
|
||||
var level = initial
|
||||
bot.fullRestore()
|
||||
|
||||
bot.skills.setStaticLevel(Skills.STRENGTH, level)
|
||||
bot.skills.setStaticLevel(Skills.DEFENCE, level)
|
||||
bot.skills.setStaticLevel(Skills.ATTACK, level)
|
||||
bot.skills.setStaticLevel(Skills.HITPOINTS, level)
|
||||
bot.skills.setStaticLevel(Skills.PRAYER, 70)
|
||||
bot.skills.setStaticLevel(Skills.RANGE, 10)
|
||||
bot.skills.setStaticLevel(Skills.MAGIC, 10)
|
||||
bot.skills.setLevel(Skills.STRENGTH, level)
|
||||
bot.skills.setLevel(Skills.DEFENCE, level)
|
||||
bot.skills.setLevel(Skills.ATTACK, level)
|
||||
bot.skills.setLevel(Skills.HITPOINTS, level)
|
||||
bot.skills.setLevel(Skills.PRAYER, 70)
|
||||
bot.skills.setLevel(Skills.RANGE, 10)
|
||||
bot.skills.setLevel(Skills.MAGIC, 10)
|
||||
bot.skills.updateCombatLevel()
|
||||
equipHighest(bot, PCMELEE_HELMS)
|
||||
equipHighest(bot, PCMELEE_LEG)
|
||||
equipHighest(bot, PCMELEE_SHIELD)
|
||||
equipHighest(bot, PCMELEE_TOP)
|
||||
equipHighest(bot, PCMELEE_WEP)
|
||||
bot.equipment.refresh()
|
||||
equipHighest(bot, CAPE)
|
||||
equipHighest(bot, NECK)
|
||||
equipHighest(bot, GLOVES)
|
||||
equipHighest(bot, BOOTS)
|
||||
equipHighest(bot, RING_BERS)
|
||||
bot.equipment.refresh()
|
||||
bot.skills.setStaticLevel(Skills.DEFENCE, 70)
|
||||
bot.skills.setStaticLevel(Skills.ATTACK, 99)
|
||||
bot.skills.setStaticLevel(Skills.STRENGTH, 99)
|
||||
bot.skills.setStaticLevel(Skills.HITPOINTS, 80)
|
||||
bot.skills.setLevel(Skills.DEFENCE, 70)
|
||||
bot.skills.setLevel(Skills.ATTACK, 99)
|
||||
bot.skills.setLevel(Skills.STRENGTH, 99)
|
||||
bot.skills.setLevel(Skills.HITPOINTS, 80)
|
||||
bot.fullRestore()
|
||||
}
|
||||
/**
|
||||
* Gears a Intermediate Pest control bot.
|
||||
* @author Sir Kermit & Ceikry
|
||||
*/
|
||||
fun gearPCiRangedBot(bot: AIPlayer, crossbow: Boolean? = false, vararg skills: Int) {
|
||||
var max = 0
|
||||
val level = RandomFunction.random(50, 80).also {max=99 }
|
||||
bot.fullRestore()
|
||||
|
||||
bot.skills.setStaticLevel(Skills.RANGE, level)
|
||||
bot.skills.setStaticLevel(Skills.DEFENCE, 80)
|
||||
bot.skills.setStaticLevel(Skills.ATTACK, level)
|
||||
bot.skills.setStaticLevel(Skills.STRENGTH, level)
|
||||
bot.skills.setStaticLevel(Skills.HITPOINTS, 70)
|
||||
bot.skills.setStaticLevel(Skills.SUMMONING, 99)
|
||||
bot.skills.setLevel(Skills.RANGE, level)
|
||||
bot.skills.setLevel(Skills.DEFENCE, 80)
|
||||
bot.skills.setLevel(Skills.ATTACK, level)
|
||||
bot.skills.setLevel(Skills.STRENGTH, level)
|
||||
bot.skills.setLevel(Skills.HITPOINTS, 70)
|
||||
bot.skills.setLevel(Skills.SUMMONING, 99)
|
||||
bot.skills.updateCombatLevel()
|
||||
equipHighest(bot, PCRANGE_HELMS)
|
||||
equipHighest(bot, PCRANGE_TOPS)
|
||||
equipHighest(bot, PCRANGE_BACK)
|
||||
equipHighest(bot, PCRANGE_LEGS)
|
||||
bot.equipment.refresh()
|
||||
equipHighest(bot, GLOVES)
|
||||
equipHighest(bot, NECK)
|
||||
equipHighest(bot, BOOTS)
|
||||
equipHighest(bot, RING_ARCH)
|
||||
bot.equipment.refresh()
|
||||
if(crossbow == true) { equipHighest(bot,PCCROSSBOWS); equipHighest(bot,MELEE_SHIELD); bot.equipment.add(Item(Items.BRONZE_BOLTS_877,Integer.MAX_VALUE),13,false,false) }
|
||||
else {equipHighest(bot, PCBOWS); bot.equipment.add(Item(Items.BRONZE_ARROW_882,Integer.MAX_VALUE),13,false,false) }
|
||||
bot.skills.setStaticLevel(Skills.RANGE, 99)
|
||||
bot.skills.setLevel(Skills.RANGE, 99)
|
||||
bot.equipment.refresh()
|
||||
}
|
||||
|
||||
fun gearPCiMeleeBot(bot: AIPlayer, vararg skills: Int){
|
||||
var max = 0
|
||||
val initial = RandomFunction.random(55, 95).also {max=95 }
|
||||
var level = initial
|
||||
bot.fullRestore()
|
||||
|
||||
bot.skills.setStaticLevel(Skills.STRENGTH, level)
|
||||
bot.skills.setStaticLevel(Skills.DEFENCE, level)
|
||||
bot.skills.setStaticLevel(Skills.ATTACK, level)
|
||||
bot.skills.setStaticLevel(Skills.HITPOINTS, level)
|
||||
bot.skills.setStaticLevel(Skills.PRAYER, 99)
|
||||
bot.skills.setStaticLevel(Skills.RANGE, level)
|
||||
bot.skills.setStaticLevel(Skills.MAGIC, level)
|
||||
bot.skills.setStaticLevel(Skills.SUMMONING, 99)
|
||||
bot.skills.setLevel(Skills.STRENGTH, level)
|
||||
bot.skills.setLevel(Skills.DEFENCE, level)
|
||||
bot.skills.setLevel(Skills.ATTACK, level)
|
||||
bot.skills.setLevel(Skills.HITPOINTS, level)
|
||||
bot.skills.setLevel(Skills.PRAYER, 99)
|
||||
bot.skills.setLevel(Skills.RANGE, level)
|
||||
bot.skills.setLevel(Skills.MAGIC, level)
|
||||
bot.skills.setLevel(Skills.SUMMONING, 99)
|
||||
bot.skills.updateCombatLevel()
|
||||
equipHighest(bot, PCMELEE_HELMS)
|
||||
equipHighest(bot, PCMELEE_LEG)
|
||||
equipHighest(bot, PCMELEE_SHIELD)
|
||||
equipHighest(bot, PCMELEE_TOP)
|
||||
equipHighest(bot, PCMELEE_WEP)
|
||||
bot.equipment.refresh()
|
||||
equipHighest(bot, CAPE)
|
||||
equipHighest(bot, NECK)
|
||||
equipHighest(bot, GLOVES)
|
||||
equipHighest(bot, BOOTS)
|
||||
equipHighest(bot, RING_BERS)
|
||||
bot.equipment.refresh()
|
||||
bot.skills.setStaticLevel(Skills.DEFENCE, 99)
|
||||
bot.skills.setStaticLevel(Skills.ATTACK, 99)
|
||||
bot.skills.setStaticLevel(Skills.STRENGTH, 99)
|
||||
bot.skills.setStaticLevel(Skills.HITPOINTS, 99)
|
||||
bot.skills.setLevel(Skills.DEFENCE, 99)
|
||||
bot.skills.setLevel(Skills.ATTACK, 99)
|
||||
bot.skills.setLevel(Skills.STRENGTH, 99)
|
||||
bot.skills.setLevel(Skills.HITPOINTS, 99)
|
||||
bot.fullRestore()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates the stats for a bot with the given tier.
|
||||
* @param bot the bot to gear
|
||||
* @param tier the Tier of stats and gear. LOW, MED, HIGH, PURE.
|
||||
* @param skills the skills that we should generate.
|
||||
* @author Ceikry and Eli
|
||||
*/
|
||||
fun generateStats(bot: AIPlayer, tier: Tier, vararg skills: Int) {
|
||||
var totalXPAdd = 0.0
|
||||
var skillAmt = 0.0
|
||||
val variance = 0.5
|
||||
var max = 0
|
||||
val initial = when (tier) {
|
||||
Tier.LOW -> RandomFunction.random(33).also { max = 33 }
|
||||
Tier.MED -> RandomFunction.random(33, 66).also { max = 66 }
|
||||
Tier.HIGH -> RandomFunction.random(66, 99).also { max = 99 }
|
||||
Tier.PURE -> RandomFunction.random(90, 99).also {max = 99 }
|
||||
}
|
||||
for (skill in skills.indices) {
|
||||
val perc = RandomFunction.random(-variance,variance)
|
||||
var level = initial + (perc * 33).toInt()
|
||||
if(level < 1)
|
||||
level = 1
|
||||
if(level > max)
|
||||
level = max
|
||||
bot.skills.setLevel(skills[skill], level)
|
||||
bot.skills.setStaticLevel(skills[skill], level)
|
||||
totalXPAdd += bot.skills.getExperience(skills[skill])
|
||||
skillAmt++
|
||||
}
|
||||
when (tier){
|
||||
Tier.PURE -> {
|
||||
bot.skills.setStaticLevel(Skills.DEFENCE,10)
|
||||
bot.skills.setStaticLevel(Skills.STRENGTH,99)
|
||||
bot.skills.setStaticLevel(Skills.ATTACK,90)
|
||||
bot.skills.setStaticLevel(Skills.PRAYER,43)
|
||||
bot.skills.setStaticLevel(Skills.RANGE,1)
|
||||
bot.skills.setStaticLevel(Skills.MAGIC,1)
|
||||
}
|
||||
}
|
||||
|
||||
bot.skills.addExperience(Skills.HITPOINTS, (totalXPAdd / skillAmt) * 0.2)
|
||||
val new_hp = bot.skills.levelFromXP((totalXPAdd / skillAmt) * 0.2)
|
||||
bot.skills.setStaticLevel(Skills.HITPOINTS,10 + new_hp)
|
||||
bot.skills.updateCombatLevel()
|
||||
bot.fullRestore()
|
||||
}
|
||||
|
||||
/**
|
||||
* Equips the highest piece of gear from a set for the stats the bot has.
|
||||
* @param bot the bot to equip gear to.
|
||||
* @param set the IntArray set to check
|
||||
* @param levelcap (optional) the max level of gear to equip.
|
||||
* @author Ceikry
|
||||
*/
|
||||
private fun equipHighest(bot: AIPlayer, set: Array<Int>, levelcap: Int? = null) {
|
||||
val highestItems = ArrayList<Item>()
|
||||
var highest: Item? = null
|
||||
for (i in set.indices) {
|
||||
val item = Item(set[i])
|
||||
var canEquip = true
|
||||
(item.definition.handlers.getOrDefault("requirements",null) as HashMap<Int,Int>?)?.let { map ->
|
||||
levelcap?.let {levelcap ->
|
||||
map.map {
|
||||
if (bot.skills.getLevel(it.key) < it.value || it.value > levelcap)
|
||||
canEquip = false
|
||||
}
|
||||
} ?: map.map {
|
||||
if (bot.skills.getLevel(it.key) < it.value)
|
||||
canEquip = false
|
||||
}
|
||||
}
|
||||
if (canEquip) {
|
||||
if (highest == null) {
|
||||
highest = item
|
||||
highestItems.add(item)
|
||||
continue
|
||||
}
|
||||
if (item.lvlAvg() > highest.lvlAvg()) {
|
||||
highest = item
|
||||
highestItems.clear()
|
||||
highestItems.add(item)
|
||||
} else if(item.lvlAvg() == highest.lvlAvg()){
|
||||
highestItems.add(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
bot.equipment.add(highestItems.random(), highest!!.definition!!.handlers["equipment_slot"] as Int, false, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension function for the Item class that gets the average of its level requirements.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun Item.lvlAvg(): Int {
|
||||
var total = 1
|
||||
var count = 1
|
||||
(definition.handlers.getOrDefault("requirements",null) as HashMap<Int,Int>?)?.let { map ->
|
||||
map.map {
|
||||
total += it.value
|
||||
count++
|
||||
}
|
||||
}
|
||||
return total / count
|
||||
}
|
||||
|
||||
|
||||
val RANGE_HELMS = arrayOf(1167,4732,3749)
|
||||
val RANGE_TOPS = arrayOf(1129,1131,1135,2499,2501,2503)
|
||||
val RANGE_LEGS = arrayOf(1095,1097,1099,2493,2495,2497)
|
||||
val BOWS = arrayOf(841,843,847,853)
|
||||
val CROSSBOWS = arrayOf(9185,9174,9177,9176,9179,9181,9183)
|
||||
|
||||
val PCRANGE_HELMS = arrayOf(1167,4732,3749,11718)
|
||||
val PCRANGE_TOPS = arrayOf(1129,1131,1135,2503,11720)
|
||||
val PCRANGE_LEGS = arrayOf(1095,1097,1099,2497,11722)
|
||||
val PCRANGE_BACK = arrayOf(1019,10498,10499)
|
||||
val PCBOWS = arrayOf(841,843,847,853)
|
||||
val PCCROSSBOWS = arrayOf(9185,9174,9177,9176,9179,9181,9183)
|
||||
|
||||
val MELEE_HELMS = arrayOf(1137,1139, 1141, 6621, 1143,1145,1147,1149,1151,1153, 6623, 1159,1163,1165,3748, 3751, 3753, 4716,4724, 4745, 4753)
|
||||
val MELEE_TOP = arrayOf(1101,1103,1105,1107,1109,1111,1113,2513,1115,1117,1119,1121,1123,1125,1127,4720,4728,4749,4749)
|
||||
val MELEE_LEG = arrayOf(1081,1083,1085,1087,1089,1091,1093,4759,1067,1069,1071,1073,1075,1077,1079,4722,4751,4722,4751)
|
||||
val MELEE_SHIELD = arrayOf(1171,1173,1175,1177,1179,1181,1183,1185,1187,1189,1191,1193,1195,1197,1199,1201)
|
||||
val MELEE_WEP = arrayOf(1277,1279,1281,1283,1285,1287,1289,1291,1293,1295,1297,1299,1301,1303,1305,1321,1323,1325,1327,1329,1331,1333,4587,4151,1363,1365,1367,1369,1371,1373,1375,1377)
|
||||
val NGLOVES = arrayOf(1059,2922,2912,2902,2932,2942,3799)
|
||||
val NBOOTS = arrayOf(4121,4123,4125,4127,4129,4131,1061,1837,2579,9005)
|
||||
val NRBOOTS = arrayOf(9006,626,628,630,632,634)
|
||||
val NNECK = arrayOf(1704,1725,1729,1731)
|
||||
val NRANGENECK = arrayOf(1478,1704)
|
||||
val NRANGESHIELD = arrayOf(1191,1193,1195,1197,1199,1201)
|
||||
|
||||
val PCMELEE_HELMS = arrayOf(1137,1139, 1141, 6621, 1143,1145,1147,1149,1151,1153, 6623, 1159,1163,1165,3748, 3751, 10828, 11335, 3753, 4716,4724, 4745, 4753, 3751)
|
||||
val PCMELEE_TOP = arrayOf(1101,1103,1105,1107,1109,1111,1113,2513,1115,1117,1119,1121,1123,1125,1127,4720,4728,4749,4749,11724,14479,2513)
|
||||
val PCMELEE_LEG = arrayOf(1081,1083,1085,1087,1089,1091,1093,4759,1067,1069,1071,1073,1075,1077,1079,4722,4751,4722,4751,11726,4087)
|
||||
val PCMELEE_SHIELD = arrayOf(1171,1173,1175,1177,1179,1181,1183,1185,1187,1189,1191,1193,1195,1197,1199,1201,6524,13742,13740,13738,13736,13734)
|
||||
val PCMELEE_WEP = arrayOf(1277,1279,1281,1283,1285,1287,1289,1291,1293,1295,1297,1299,1301,1303,1305,1321,1323,1325,1327,1329,1331,1333,4587,4151,1363,1365,1367,1369,1371,1373,1375,1377,1434,5698)
|
||||
|
||||
val NECK = arrayOf(1704,6585)
|
||||
val CAPE = arrayOf(1019,1021,1023,6568,4315,4317,4319,4321,4323,4325,4327,4329,4331,4333,4335,4337,4339,4341,4343,4345,4347,4349,4351)
|
||||
val GLOVES = arrayOf(1059,7456,7457,7458,7459,7460,7461,7462)
|
||||
val BOOTS = arrayOf(1061,4131,11732,11728,4131)
|
||||
val RING_BERS = arrayOf(6737)
|
||||
val RING_ARCH = arrayOf(6733)
|
||||
|
||||
val RICH_MELEE_HELMS = arrayOf(2587,2595,2605,2613,2619,2627,2657,2665,2673,3486,1149,10828,4716,4724,4753)
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package core.game.bots
|
||||
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.system.task.Pulse
|
||||
import core.game.world.GameWorld
|
||||
import core.game.world.map.Location
|
||||
import core.tools.RandomFunction
|
||||
import core.Server
|
||||
import content.global.bots.Idler
|
||||
|
||||
class GeneralBotCreator {
|
||||
//org/crandor/net/packet/in/InteractionPacket.java <<< This is a very useful class for learning to code bots
|
||||
constructor(botScript: Script, loc: Location?) {
|
||||
botScript.bot = AIPBuilder.create(loc)
|
||||
GameWorld.Pulser.submit(BotScriptPulse(botScript))
|
||||
}
|
||||
|
||||
constructor(botScript: Script, bot: AIPlayer?) {
|
||||
botScript.bot = bot
|
||||
GameWorld.Pulser.submit(BotScriptPulse(botScript).also { AIRepository.PulseRepository[it.botScript.bot.username.lowercase()] = it })
|
||||
}
|
||||
|
||||
constructor(botScript: Script, player: Player, isPlayer: Boolean){
|
||||
botScript.bot = player
|
||||
val pulse = BotScriptPulse(botScript,isPlayer)
|
||||
GameWorld.Pulser.submit(pulse)
|
||||
player.setAttribute("/save:not_on_highscores",true)
|
||||
player.setAttribute("botting:script",pulse)
|
||||
}
|
||||
|
||||
inner class BotScriptPulse(public val botScript: Script, val isPlayer: Boolean = false) : Pulse(1){
|
||||
var ticks = 0
|
||||
init {
|
||||
botScript.init(isPlayer)
|
||||
}
|
||||
var randomDelay = 0
|
||||
|
||||
override fun pulse(): Boolean {
|
||||
if(randomDelay > 0){
|
||||
randomDelay -= 1
|
||||
return false
|
||||
}
|
||||
if (!botScript.bot.pulseManager.hasPulseRunning()) {
|
||||
|
||||
/*if (ticks++ >= RandomFunction.random(90000,120000)) {
|
||||
AIPlayer.deregister(botScript.bot.uid)
|
||||
ticks = 0
|
||||
SystemLogger.log("Submitting transition pulse from ticks")
|
||||
GameWorld.Pulser.submit(TransitionPulse(botScript))
|
||||
return true
|
||||
}*/
|
||||
if(!botScript.running) return true //has to be separated this way or it double-submits the respawn pulse.
|
||||
|
||||
|
||||
val idleRoll = RandomFunction.random(50)
|
||||
if(idleRoll == 2 && botScript !is Idler){
|
||||
randomDelay += RandomFunction.random(2,20)
|
||||
return false
|
||||
}
|
||||
botScript.tick()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
ticks = Integer.MAX_VALUE - 20 //Sets the ticks as high as they can go (safely) and then runs pulse again
|
||||
pulse() //to trigger the transition pulse to be submitted.
|
||||
super.stop()
|
||||
if (Server.running) AIRepository.PulseRepository.remove(this.botScript.bot.username.lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
inner class TransitionPulse(val script: Script) : Pulse(RandomFunction.random(60,200)){
|
||||
override fun pulse(): Boolean {
|
||||
// This does not get called and should be removed
|
||||
GameWorld.Pulser.submit(BotScriptPulse(script.newInstance()).also { AIRepository.PulseRepository[it.botScript.bot.username.lowercase()] = it })
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package core.game.bots
|
||||
|
||||
annotation class PlayerCompatible
|
||||
@@ -0,0 +1,6 @@
|
||||
package core.game.bots
|
||||
|
||||
object PlayerScripts {
|
||||
class PlayerScript(val identifier: String, val description: Array<String>, val name: String, val clazz: Class<*>)
|
||||
val identifierMap = HashMap<String, PlayerScript>()
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package core.game.bots;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import core.game.consumable.Consumable;
|
||||
import content.data.consumables.Consumables;
|
||||
import core.game.consumable.Food;
|
||||
import content.data.consumables.effects.HealingEffect;
|
||||
import core.game.node.entity.skill.Skills;
|
||||
import core.game.node.entity.Entity;
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.entity.player.link.prayer.PrayerType;
|
||||
import core.game.node.item.Item;
|
||||
import core.game.world.map.Location;
|
||||
import core.game.world.map.RegionManager;
|
||||
import core.tools.RandomFunction;
|
||||
|
||||
public class PvMBots extends AIPlayer {
|
||||
|
||||
//Used so the bot doesn't spam actions
|
||||
private int tick = 0;
|
||||
|
||||
public PvMBots(Location l) {
|
||||
super(l);
|
||||
}
|
||||
|
||||
public PvMBots(String copyFromFile, Location l) {
|
||||
super(copyFromFile, l);
|
||||
}
|
||||
|
||||
|
||||
public List<Entity> FindTargets(Entity entity, int radius) {
|
||||
List<Entity> targets = new ArrayList<>(20);
|
||||
Object[] localNPCs = RegionManager.getLocalNpcs(entity,radius).toArray();
|
||||
int length = localNPCs.length;
|
||||
if(length > 5){length = 5;}
|
||||
for (int i = 0; i < length; i++) {
|
||||
NPC npc = (NPC) localNPCs[i];
|
||||
{
|
||||
if (checkValidTargets(npc))
|
||||
targets.add(npc);
|
||||
}
|
||||
}
|
||||
if (targets.size() == 0)
|
||||
return null;
|
||||
return targets;
|
||||
}
|
||||
|
||||
public boolean checkValidTargets(NPC target) {
|
||||
if (!target.isActive()) {
|
||||
return false;
|
||||
}
|
||||
if (!target.getProperties().isMultiZone() && target.inCombat()) {
|
||||
return false;
|
||||
}
|
||||
if (!target.getDefinition().hasAction("attack")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean AttackNpcsInRadius(int radius)
|
||||
{
|
||||
return AttackNpcsInRadius(this, radius);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attacks NPCs in radius of bot
|
||||
* @param bot
|
||||
* @param radius
|
||||
* @return true if bot will be fighting
|
||||
*/
|
||||
public boolean AttackNpcsInRadius(Player bot, int radius) {
|
||||
if (bot.inCombat())
|
||||
return true;
|
||||
List<Entity> creatures = FindTargets(bot, radius);
|
||||
if (creatures == null) {
|
||||
return false;
|
||||
}
|
||||
bot.attack(creatures.get(RandomFunction.getRandom((creatures.size() - 1))));
|
||||
if (!creatures.isEmpty()) {
|
||||
return true;
|
||||
} else {
|
||||
creatures = FindTargets(bot, radius);
|
||||
if (!creatures.isEmpty())
|
||||
{
|
||||
bot.attack(creatures.get(RandomFunction.getRandom((creatures.size() - 1))));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
|
||||
this.tick++;
|
||||
|
||||
//Despawn
|
||||
if (this.getSkills().getLifepoints() <= 5){
|
||||
//TODO: Just respawn a new bot (not sure how you'd do that :L)
|
||||
// Maybe make all PvMBots know what to do if they aren't in right area? I.e. pest control bots teleport to PC
|
||||
//this.teleport(new Location(500, 500));
|
||||
//Despawning not being delayed causes 3 errors in the console
|
||||
this.getSkills().setLifepoints(20);
|
||||
}
|
||||
|
||||
//Npc Combat
|
||||
/*if (this.tick % 10 == 0) {
|
||||
if (!this.inCombat())
|
||||
AttackNpcsInRadius(this, 5);
|
||||
}*/
|
||||
|
||||
if (this.tick == 100) this.tick = 0;
|
||||
|
||||
//this.eat();
|
||||
//this.getPrayer().toggle()
|
||||
}
|
||||
|
||||
public void CheckPrayer(PrayerType type[]) {
|
||||
for (int i = 0; i < type.length; i++)
|
||||
this.getPrayer().toggle(type[i]);
|
||||
}
|
||||
|
||||
public void eat(int foodId) {
|
||||
Item foodItem = new Item(foodId);
|
||||
|
||||
if ((this.getSkills().getStaticLevel(Skills.HITPOINTS) >= this.getSkills().getLifepoints() * 3) && this.getInventory().containsItem(foodItem)) {
|
||||
this.lock(3);
|
||||
//this.animate(new Animation(829));
|
||||
Item food = this.getInventory().getItem(foodItem);
|
||||
|
||||
Consumable consumable = Consumables.getConsumableById(food.getId());
|
||||
|
||||
if (consumable == null) {
|
||||
consumable = new Food(new int[] {food.getId()}, new HealingEffect(1));
|
||||
}
|
||||
|
||||
consumable.consume(food, this);
|
||||
this.getProperties().getCombatPulse().delayNextAttack(3);
|
||||
}
|
||||
if (this.checkVictimIsPlayer() == false)
|
||||
if (!(this.getInventory().contains(foodId, 1)))
|
||||
this.getInventory().add(new Item(foodId, 5)); //Add Food to Inventory
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package core.game.bots
|
||||
|
||||
import core.game.world.map.Location
|
||||
import content.minigame.pestcontrol.bots.PestControlTestBot
|
||||
import content.minigame.pestcontrol.bots.PestControlTestBot2
|
||||
|
||||
// import sun.util.resources.CalendarData;
|
||||
class PvMBotsBuilder {
|
||||
|
||||
companion object {
|
||||
var botsSpawned = 0
|
||||
|
||||
fun create(l: Location?): PvMBots {
|
||||
botsSpawned++
|
||||
return PvMBots(l)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun createPestControlTestBot2(l: Location?): PestControlTestBot2 {
|
||||
botsSpawned++
|
||||
return PestControlTestBot2(l!!)
|
||||
}
|
||||
@JvmStatic
|
||||
fun createPestControlTestBot(l: Location?): PestControlTestBot {
|
||||
botsSpawned++
|
||||
return PestControlTestBot(l!!)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package core.game.bots;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class Script {
|
||||
|
||||
public ScriptAPI scriptAPI;
|
||||
public ArrayList<Item> inventory = new ArrayList<>(20);
|
||||
public ArrayList<Item> equipment = new ArrayList<>(20);
|
||||
public Map<Integer, Integer> skills = new HashMap<>();
|
||||
|
||||
|
||||
public Player bot;
|
||||
|
||||
public boolean running = true;
|
||||
|
||||
public void init(boolean isPlayer)
|
||||
{
|
||||
//bot.init();
|
||||
scriptAPI = new ScriptAPI(bot);
|
||||
|
||||
if(!isPlayer) {
|
||||
for (Item i : equipment) {
|
||||
bot.getEquipment().add(i, true, false);
|
||||
}
|
||||
bot.getInventory().clear();
|
||||
for (Item i : inventory) {
|
||||
bot.getInventory().add(i);
|
||||
}
|
||||
for (Map.Entry<Integer, Integer> skill : skills.entrySet()) {
|
||||
setLevel(skill.getKey(), skill.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return bot.getName() + " is a " + this.getClass().getSimpleName() + " at location " + bot.getLocation().toString() + " Current pulse: " + bot.getPulseManager().getCurrent();
|
||||
}
|
||||
|
||||
public abstract void tick();
|
||||
|
||||
public void setLevel(int skill, int level) {
|
||||
bot.getSkills().setLevel(skill, level);
|
||||
bot.getSkills().setStaticLevel(skill, level);
|
||||
bot.getSkills().updateCombatLevel();
|
||||
bot.getAppearance().sync();
|
||||
}
|
||||
|
||||
// This does not get called and all implementations should be removed
|
||||
public abstract Script newInstance();
|
||||
}
|
||||
@@ -0,0 +1,827 @@
|
||||
package core.game.bots
|
||||
|
||||
import core.api.*
|
||||
import core.cache.def.impl.ItemDefinition
|
||||
import core.game.component.Component
|
||||
import core.game.consumable.Consumable
|
||||
import content.data.consumables.Consumables
|
||||
import core.game.consumable.Food
|
||||
import content.data.consumables.effects.HealingEffect
|
||||
import core.game.interaction.DestinationFlag
|
||||
import core.game.interaction.MovementPulse
|
||||
import core.game.interaction.Option
|
||||
import core.game.node.Node
|
||||
import core.game.node.scenery.Scenery
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.node.entity.npc.NPC
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.entity.skill.Skills
|
||||
import core.game.node.item.GroundItem
|
||||
import core.game.node.item.GroundItemManager
|
||||
import core.game.node.item.Item
|
||||
import core.game.system.task.Pulse
|
||||
import core.game.world.map.Location
|
||||
import core.game.world.map.RegionManager
|
||||
import core.game.world.map.path.Pathfinder
|
||||
import core.game.world.update.flag.context.Animation
|
||||
import core.game.world.update.flag.context.ChatMessage
|
||||
import core.game.world.update.flag.context.Graphics
|
||||
import core.game.world.update.flag.player.ChatFlag
|
||||
import core.tools.RandomFunction
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.json.simple.JSONArray
|
||||
import org.json.simple.JSONObject
|
||||
import org.rs09.consts.Items
|
||||
import core.ServerConstants.Companion.SERVER_GE_NAME
|
||||
import core.game.ge.GrandExchange
|
||||
import core.game.ge.GrandExchangeOffer
|
||||
import core.game.interaction.IntType
|
||||
import core.game.interaction.InteractionListeners
|
||||
import core.tools.SystemLogger
|
||||
import core.game.system.config.ItemConfigParser
|
||||
import core.game.world.GameWorld
|
||||
import core.game.world.repository.Repository
|
||||
import core.tools.colorize
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import kotlin.math.max
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sqrt
|
||||
|
||||
class ScriptAPI(private val bot: Player) {
|
||||
val GRAPHICSUP = Graphics(1576)
|
||||
val ANIMATIONUP = Animation(8939)
|
||||
val GRAPHICSDOWN = Graphics(1577)
|
||||
val ANIMATIONDOWN = Animation(8941)
|
||||
|
||||
/**
|
||||
* Gets the distance between two nodes
|
||||
* @param n1 the first node
|
||||
* @param n2 the second node
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun distance(n1: Node, n2: Node): Double {
|
||||
return sqrt((n1.location.x - n2.location.x.toDouble()).pow(2.0) + (n2.location.y - n1.location.y.toDouble()).pow(2.0))
|
||||
}
|
||||
|
||||
fun interact(bot: Player, node: Node?, option: String){
|
||||
|
||||
if(node == null) return
|
||||
|
||||
val type = when(node){
|
||||
is Scenery -> IntType.SCENERY
|
||||
is NPC -> IntType.NPC
|
||||
is Item -> IntType.ITEM
|
||||
else -> null
|
||||
} ?: return
|
||||
val opt: Option? = node.interaction.options.filter {it != null && it.name.equals(option, true) }.firstOrNull()
|
||||
|
||||
if(opt == null){
|
||||
SystemLogger.logWarn(this::class.java, "Invalid option name provided: $option")
|
||||
return
|
||||
}
|
||||
|
||||
if(!InteractionListeners.run(node.id, type, option, bot, node)) node.interaction.handle(bot, opt)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nearest node with name entityName
|
||||
* @param entityName the name of the node to look for
|
||||
* @return the nearest node with a matching name or null
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun getNearestNode(entityName: String): Node? {
|
||||
var entity: Node? = null
|
||||
var minDistance = Double.MAX_VALUE
|
||||
for (node in RegionManager.forId(bot.location.regionId).planes[bot.location.z].entities) {
|
||||
if (node != null && node.name == entityName && distance(bot, node) < minDistance && !Pathfinder.find(bot, node).isMoveNear) {
|
||||
entity = node
|
||||
minDistance = distance(bot, node)
|
||||
}
|
||||
}
|
||||
return entity
|
||||
}
|
||||
|
||||
fun sendChat(message: String) {
|
||||
bot.sendChat(message)
|
||||
bot.updateMasks.register(ChatFlag(ChatMessage(bot, message, 0, 0)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nearest node with a name contained in the list of acceptable names
|
||||
* @param acceptedNames the list of accepted npc/object names
|
||||
* @return the nearest node with a matching name or null
|
||||
* @author Ceikry
|
||||
*/
|
||||
|
||||
fun getNearestNodeFromList(acceptedNames: List<String>, isObject: Boolean): Node? {
|
||||
if (isObject) {
|
||||
var entity: Node? = null
|
||||
var minDistance = Double.MAX_VALUE
|
||||
for (objects in RegionManager.forId(bot.location.regionId).planes[bot.location.z].objects) {
|
||||
for(e in objects) {
|
||||
val name = e?.name
|
||||
if (e != null && acceptedNames.contains(name) && distance(bot, e) < minDistance && !Pathfinder.find(bot, e).isMoveNear && e.isActive) {
|
||||
entity = e
|
||||
minDistance = distance(bot, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
return if(entity == null) null else entity as Scenery
|
||||
} else {
|
||||
var entity: Node? = null
|
||||
var minDistance = Double.MAX_VALUE
|
||||
for (e in RegionManager.forId(bot.location.regionId).planes[bot.location.z].entities) {
|
||||
val name = e?.name
|
||||
if (e != null && acceptedNames.contains(name) && distance(bot, e) < minDistance && !Pathfinder.find(bot, e).isMoveNear) {
|
||||
entity = e
|
||||
minDistance = distance(bot, e)
|
||||
}
|
||||
}
|
||||
return entity
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nearest node with matching id.
|
||||
* @param id the id to look for
|
||||
* @param object whether or not the node we are looking for is an object.
|
||||
* @return the closest node with matching id or null.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun getNearestNode(id: Int, `object`: Boolean): Node? {
|
||||
if (`object`) {
|
||||
var entity: Node? = null
|
||||
var minDistance = Double.MAX_VALUE
|
||||
for (objects in RegionManager.forId(bot.location.regionId).planes[bot.location.z].objects) {
|
||||
for(e in objects) {
|
||||
if (e != null && e.id == id && distance(bot, e) < minDistance && !Pathfinder.find(bot, e).isMoveNear && e.isActive) {
|
||||
entity = e
|
||||
minDistance = distance(bot, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
return if(entity == null) null else entity as Scenery
|
||||
} else {
|
||||
var entity: Node? = null
|
||||
var minDistance = Double.MAX_VALUE
|
||||
for (e in RegionManager.forId(bot.location.regionId).planes[bot.location.z].entities) {
|
||||
if (e != null && e.id == id && distance(bot, e) < minDistance && !Pathfinder.find(bot, e).isMoveNear) {
|
||||
entity = e
|
||||
minDistance = distance(bot, e)
|
||||
}
|
||||
}
|
||||
return entity
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nearest node with a matching name.
|
||||
* @param name the name to look for.
|
||||
* @param object whether or not the node we are looking for is an object.
|
||||
* @return the nearest matching node or null.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun getNearestNode(name: String, `object`: Boolean): Node? {
|
||||
if (`object`) {
|
||||
var entity: Node? = null
|
||||
var minDistance = Double.MAX_VALUE
|
||||
for (objects in RegionManager.forId(bot.location.regionId).planes[bot.location.z].objects) {
|
||||
for(e in objects) {
|
||||
if (e != null && e.name.toLowerCase() == name.toLowerCase() && distance(bot, e) < minDistance && !Pathfinder.find(bot, e).isMoveNear && e.isActive) {
|
||||
entity = e
|
||||
minDistance = distance(bot, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
return if(entity == null) null else entity as Scenery
|
||||
} else {
|
||||
var entity: Node? = null
|
||||
var minDistance = Double.MAX_VALUE
|
||||
for (e in RegionManager.forId(bot.location.regionId).planes[bot.location.z].entities) {
|
||||
if (e != null && e.name.toLowerCase() == name.toLowerCase() && distance(bot, e) < minDistance && !Pathfinder.find(bot, e).isMoveNear) {
|
||||
entity = e
|
||||
minDistance = distance(bot, e)
|
||||
}
|
||||
}
|
||||
return entity
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nearest ground item with matching ID from the list in AIRepository.
|
||||
* @param id the id of the ground item we are looking for.
|
||||
* @return the nearest GroundItem with a matching id or null
|
||||
* @author Ceikry
|
||||
*/
|
||||
private fun getNearestGroundItem(id: Int): GroundItem? {
|
||||
var distance = 11.0
|
||||
var closest: GroundItem? = null
|
||||
if(AIRepository.getItems(bot) != null) {
|
||||
for (item in AIRepository.getItems(bot)!!.filter { it: GroundItem -> it.distance(bot.location) < 10 }) {
|
||||
if (item.id == id) {
|
||||
//distance = item.distance(bot.location)
|
||||
closest = item
|
||||
}
|
||||
}
|
||||
if (!GroundItemManager.getItems().contains(closest)){
|
||||
AIRepository.getItems(bot)?.remove(closest)
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
val items: ArrayList<GroundItem>? = bot.getAttribute("botting:drops",null)
|
||||
if(items != null){
|
||||
for(item in items.filter { it.distance(bot.location) < 10 }){
|
||||
if(item.id == id) return item.also { items.remove(item); bot.setAttribute("botting:drops",items) }
|
||||
}
|
||||
}
|
||||
}
|
||||
return closest
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the nearest ground item with a matching id if it exists.
|
||||
* @param id the id to look for
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun takeNearestGroundItem(id: Int) : Boolean{
|
||||
val item = getNearestGroundItem(id)
|
||||
if(item != null){
|
||||
item.interaction?.handle(bot, item.interaction[2])
|
||||
return true
|
||||
}
|
||||
else return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nearest GameObject to loc with matching objectId
|
||||
* @param loc the location we are checking around
|
||||
* @param objectId the id of the object we are looking for
|
||||
* @return the nearest matching object or null.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun getNearestGameObject(loc: Location, objectId: Int): Scenery? {
|
||||
var nearestObject: Scenery? = null
|
||||
val minDistance = Double.MAX_VALUE
|
||||
for (o in RegionManager.forId(loc.regionId).planes[0].objects) {
|
||||
for (obj in o) {
|
||||
if (obj != null) {
|
||||
if (distance(loc, obj) < minDistance && obj.id == objectId) {
|
||||
nearestObject = obj
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nearestObject
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of NPCs that are attackable around the entity.
|
||||
* @param entity the entity to search around.
|
||||
* @param radius the radius around entity to search in.
|
||||
* @param name the name of the NPC to look for.
|
||||
* @return an Array of the nearest NPCs with matching name, or null.
|
||||
* @author Ceikry
|
||||
*/
|
||||
private fun findTargets(entity: Entity, radius: Int, name: String? = null): List<Entity>? {
|
||||
val targets: MutableList<Entity> = ArrayList()
|
||||
val localNPCs: Array<Any> = RegionManager.getLocalNpcs(entity, radius).toTypedArray()
|
||||
var length = localNPCs.size
|
||||
if (length > 5) {
|
||||
length = 5
|
||||
}
|
||||
for (i in 0 until length) {
|
||||
val npc = localNPCs[i] as NPC
|
||||
run { if (checkValidTargets(npc, name)) targets.add(npc) }
|
||||
}
|
||||
return if (targets.size == 0) null else targets
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given target is a valid target for a combat bot.
|
||||
* @param target the NPC that we are checking
|
||||
* @param name the expected name of the NPC.
|
||||
* @return true if the target is valid, false if not.
|
||||
* @author Ceikry
|
||||
*/
|
||||
private fun checkValidTargets(target: NPC, name: String?): Boolean {
|
||||
if (!target.isActive) {
|
||||
return false
|
||||
}
|
||||
if (!target.properties.isMultiZone && target.inCombat()) {
|
||||
return false
|
||||
}
|
||||
if (name != null){
|
||||
if(target.name != name)
|
||||
return false
|
||||
}
|
||||
return target.definition.hasAction("attack")
|
||||
}
|
||||
|
||||
/**
|
||||
* Attacks npcs in the given radius of the bot.
|
||||
* @param bot the bot that is attacking
|
||||
* @param radius the radius to attack in
|
||||
* @return true if successfully attacking an NPC within that radius, false if not.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun attackNpcsInRadius(bot: Player, radius: Int): Boolean {
|
||||
if (bot.inCombat()) return true
|
||||
var creatures: List<Entity>? = findTargets(bot, radius) ?: return false
|
||||
bot.attack(creatures!![RandomFunction.getRandom(creatures.size - 1)])
|
||||
return if (creatures.isNotEmpty()) {
|
||||
true
|
||||
} else {
|
||||
creatures = findTargets(bot, radius)
|
||||
if (!creatures!!.isEmpty()) {
|
||||
bot.attack(creatures[RandomFunction.getRandom(creatures.size - 1)])
|
||||
return true
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to iteratively walk a bot to a faraway location. Limited by doors and large obstacles like mountains.
|
||||
* @param loc the location to walk to.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun walkTo(loc: Location){
|
||||
if(!bot.walkingQueue.isMoving) {
|
||||
GlobalScope.launch {
|
||||
walkToIterator(loc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to iteratively walk an array of Locations to get over complex obstacles.
|
||||
* @param steps the array of locations to walk to.
|
||||
* @author dginovker
|
||||
*/
|
||||
fun walkArray(steps: Array<Location>){
|
||||
bot.pulseManager.run(object : Pulse(){
|
||||
var stepIndex = 0
|
||||
override fun pulse(): Boolean {
|
||||
// If the stepIndex is out of bounds, we're done
|
||||
if(stepIndex >= steps.size) return true
|
||||
// If we're near the last step, we're done
|
||||
if (bot.location.withinDistance(steps[steps.size - 1], 2)) {
|
||||
return true
|
||||
}
|
||||
// If we're not near the next step, walk to it
|
||||
if (!bot.location.withinDistance(steps[stepIndex], 5)) {
|
||||
walkTo(steps[stepIndex])
|
||||
return false
|
||||
}
|
||||
// If we're near the next step, increment the step index
|
||||
stepIndex++
|
||||
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param loc the location to walk to.
|
||||
* @param radius tiles around the location the bot could walk to.
|
||||
* @author Kermit
|
||||
*/
|
||||
fun randomWalkTo(loc: Location, radius: Int) {
|
||||
if(!bot.walkingQueue.isMoving) {
|
||||
GlobalScope.launch {
|
||||
var newloc = loc.transform(RandomFunction.random(radius,-radius),
|
||||
RandomFunction.random(radius,-radius), 0)
|
||||
walkToIterator(newloc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The iterator for long-distance walking. Limited by doors and large obstacles like mountains.
|
||||
* @param loc the location to find a path to.
|
||||
* @author Ceikry
|
||||
*/
|
||||
private fun walkToIterator(loc: Location){
|
||||
var diffX = loc.x - bot.location.x
|
||||
var diffY = loc.y - bot.location.y
|
||||
while(!bot.location.transform(diffX, diffY, 0).withinDistance(bot.location)) {
|
||||
diffX /= 2
|
||||
diffY /= 2
|
||||
}
|
||||
GameWorld.Pulser.submit(object : MovementPulse(bot, bot.location.transform(diffX, diffY, 0), Pathfinder.SMART) {
|
||||
override fun pulse(): Boolean {
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Attacks npcs in the given radius of the bot.
|
||||
* @param bot the bot that is attacking
|
||||
* @param radius the radius to attack in
|
||||
* @param name the name of the NPC to target.
|
||||
* @return true if successfully attacking an NPC within that radius, false if not.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun attackNpcInRadius(bot: Player, name: String, radius: Int): Boolean {
|
||||
if (bot.inCombat()) return true
|
||||
var creatures: List<Entity>? = findTargets(bot, radius, name) ?: return false
|
||||
bot.attack(creatures!![RandomFunction.getRandom(creatures.size - 1)])
|
||||
return if (creatures.isNotEmpty()) {
|
||||
true
|
||||
} else {
|
||||
creatures = findTargets(bot, radius, name)
|
||||
if (!creatures!!.isEmpty()) {
|
||||
bot.attack(creatures.random())
|
||||
return true
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension function to find the distance between a location and a ground item.
|
||||
* @param loc the location to check the distance from.
|
||||
* @return a Double representing the distance.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun GroundItem.distance(loc: Location): Double{
|
||||
return location.getDistance(loc)
|
||||
}
|
||||
|
||||
/**
|
||||
* A function for teleporting the bot to the GE
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun teleportToGE() : Boolean{
|
||||
if (bot.isTeleBlocked) {
|
||||
return false
|
||||
}
|
||||
bot.lock()
|
||||
bot.visualize(ANIMATIONUP, GRAPHICSUP)
|
||||
bot.impactHandler.disabledTicks = 4
|
||||
val location = Location.create(3165, 3482, 0)
|
||||
bot.pulseManager.run(object : Pulse(4, bot) {
|
||||
override fun pulse(): Boolean {
|
||||
bot.unlock()
|
||||
bot.properties.teleportLocation = location
|
||||
bot.animator.reset()
|
||||
return true
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* A function for selling a given item on the GE.
|
||||
* @param id the ID of the item to sell on the GE. Pulls from the bot's bank.
|
||||
* @author Ceikry
|
||||
* @author Angle
|
||||
*/
|
||||
fun sellOnGE(id: Int){
|
||||
class toCounterPulse : MovementPulse(bot, Location.create(3165, 3487, 0)){
|
||||
override fun pulse(): Boolean {
|
||||
var actualId = id
|
||||
val itemAmt = bot.bank.getAmount(id)
|
||||
if (ItemDefinition.forId(id).noteId == id){
|
||||
actualId = Item(id).noteChange
|
||||
}
|
||||
val canSell = GrandExchange.addBotOffer(actualId, itemAmt)
|
||||
if (canSell && saleIsBigNews(actualId, itemAmt)) {
|
||||
SystemLogger.logAI(this::class.java, "Offered $itemAmt of $actualId on the GE.")
|
||||
Repository.sendNews(SERVER_GE_NAME + " just offered " + itemAmt + " " + ItemDefinition.forId(actualId).name.toLowerCase() + " on the GE.")
|
||||
}
|
||||
bot.bank.remove(Item(id, itemAmt))
|
||||
bot.bank.refresh()
|
||||
return true
|
||||
}
|
||||
}
|
||||
bot.pulseManager.run(toCounterPulse())
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to sell all items in a bot's bank on the Grand Exchange, if they are tradable.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun sellAllOnGe(){
|
||||
class toCounterPulseAll : MovementPulse(bot, Location.create(3165, 3487, 0)){
|
||||
override fun pulse(): Boolean {
|
||||
for(item in bot.bank.toArray()) {
|
||||
item ?: continue
|
||||
if (item.id == Items.LOBSTER_379) continue
|
||||
if (item.id == Items.SWORDFISH_373) continue
|
||||
if (item.id == Items.SHARK_385) continue
|
||||
if(!item.definition.isTradeable) {continue}
|
||||
val itemAmt = item.amount
|
||||
var actualId = item.id
|
||||
if (ItemDefinition.forId(actualId).noteId == actualId){
|
||||
actualId = Item(actualId).noteChange
|
||||
}
|
||||
val canSell = GrandExchange.addBotOffer(actualId, itemAmt)
|
||||
if (canSell && saleIsBigNews(actualId, itemAmt)) {
|
||||
Repository.sendNews(SERVER_GE_NAME + " just offered " + itemAmt + " " + ItemDefinition.forId(actualId).name.toLowerCase() + " on the GE.")
|
||||
}
|
||||
bot.bank.remove(item)
|
||||
bot.bank.refresh()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
bot.pulseManager.run(toCounterPulseAll())
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to sell all items in a bot's bank on the Grand Exchange, if they are tradable.
|
||||
* @author Ceikry & Kermit
|
||||
*/
|
||||
fun sellAllOnGeAdv(){
|
||||
val ge: Scenery? = getNearestNode("Desk", true) as Scenery?
|
||||
class toCounterPulseAll : MovementPulse(bot, ge, DestinationFlag.OBJECT) {
|
||||
override fun pulse(): Boolean {
|
||||
for(item in bot.bank.toArray()) {
|
||||
item ?: continue
|
||||
if(!item.definition.isTradeable) {continue}
|
||||
val itemAmt = item.amount
|
||||
var actualId = item.id
|
||||
if (ItemDefinition.forId(actualId).noteId == actualId){
|
||||
actualId = Item(actualId).noteChange
|
||||
}
|
||||
val canSell = GrandExchange.addBotOffer(actualId, itemAmt)
|
||||
if (canSell && saleIsBigNews(actualId, itemAmt)) {
|
||||
when (actualId){
|
||||
1511 -> continue
|
||||
1513 -> continue
|
||||
1515 -> continue
|
||||
1517 -> continue
|
||||
1519 -> continue
|
||||
1521 -> continue
|
||||
else -> Repository.sendNews(SERVER_GE_NAME + " just offered " + itemAmt + " " + ItemDefinition.forId(actualId).name.toLowerCase() + " on the GE.")
|
||||
}
|
||||
}
|
||||
bot.bank.remove(item)
|
||||
bot.bank.refresh()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
bot.pulseManager.run(toCounterPulseAll())
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to determine whether or not to bother everyone on the server
|
||||
* with the big news that a bot is selling something to the GE, based on item value
|
||||
* @param itemID
|
||||
* @param amount
|
||||
* @author Gexja
|
||||
*/
|
||||
fun saleIsBigNews(itemID: Int, amount: Int): Boolean {
|
||||
return ItemDefinition.forId(itemID).getAlchemyValue(true) * amount >= 500
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to teleport a bot to the given location.
|
||||
* @param loc the location to teleport to
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun teleport(loc: Location) : Boolean {
|
||||
if (bot.isTeleBlocked) {
|
||||
return false
|
||||
}
|
||||
bot.lock()
|
||||
bot.visualize(ANIMATIONUP, GRAPHICSUP)
|
||||
bot.impactHandler.disabledTicks = 4
|
||||
val location = loc
|
||||
GameWorld.Pulser.submit(object : Pulse(4, bot) {
|
||||
override fun pulse(): Boolean {
|
||||
bot.unlock()
|
||||
bot.properties.teleportLocation = location
|
||||
bot.animator.reset()
|
||||
return true
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the given item out of the bot's inventory and places it into the bank. Banks all items with matching ID.
|
||||
* @param item the ID of the item to bank
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun bankItem(item: Int){
|
||||
class BankingPulse() : Pulse(20){
|
||||
override fun pulse(): Boolean {
|
||||
val logs = bot.inventory.getAmount(item)
|
||||
bot.inventory.remove(Item(item, logs))
|
||||
bot.bank.add(Item(item, logs))
|
||||
return true
|
||||
}
|
||||
}
|
||||
bot.pulseManager.run(BankingPulse())
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes every item out of the bots inventory and places it into the bank.
|
||||
* @param none
|
||||
* @author cfunnyman joe
|
||||
*/
|
||||
fun bankAll(){
|
||||
class BankingPulse() : Pulse(20){
|
||||
override fun pulse(): Boolean {
|
||||
for(item in bot.inventory.toArray()){
|
||||
var itemAmount = bot.inventory.getAmount(item)
|
||||
|
||||
bot.inventory.remove(item)
|
||||
bot.bank.add(item)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
bot.pulseManager.run(BankingPulse())
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that makes the bot eat the food item in their inventory if their HP is below 2/3.
|
||||
* @author Ceikry
|
||||
* @param foodId the ID of the food item to eat
|
||||
*/
|
||||
fun eat(foodId: Int) {
|
||||
val foodItem = Item(foodId)
|
||||
if (bot.skills.getStaticLevel(Skills.HITPOINTS) * RandomFunction.random(0.5, 0.75) >= bot.skills.lifepoints && bot.inventory.containsItem(foodItem)) {
|
||||
bot.lock(3)
|
||||
//this.animate(new Animation(829));
|
||||
val food = bot.inventory.getItem(foodItem)
|
||||
var consumable: Consumable? = Consumables.getConsumableById(foodId)
|
||||
if (consumable == null) {
|
||||
consumable = Food(intArrayOf(food.id), HealingEffect(1))
|
||||
}
|
||||
consumable.consume(food, bot)
|
||||
bot.properties.combatPulse.delayNextAttack(3)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as the eat function, except that it forces the bot to eat regardless of HP.
|
||||
* @author Ceikry
|
||||
* @param foodId the ID of the food item to eat.
|
||||
*/
|
||||
fun forceEat(foodId: Int) {
|
||||
val foodItem = Item(foodId)
|
||||
if (bot.inventory.containsItem(foodItem)) {
|
||||
bot.lock(3)
|
||||
//this.animate(new Animation(829));
|
||||
val food = bot.inventory.getItem(foodItem)
|
||||
var consumable: Consumable? = Consumables.getConsumableById(foodId)
|
||||
if (consumable == null) {
|
||||
consumable = Food(intArrayOf(foodId), HealingEffect(1))
|
||||
}
|
||||
consumable.consume(food, bot)
|
||||
bot.properties.combatPulse.delayNextAttack(3)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for buying items off the GE.
|
||||
* @param itemID the id of the item to buy off the GE.
|
||||
* @param amount the amount to buy.
|
||||
* @return true if item was successfully bought, false if not.
|
||||
*/
|
||||
fun buyFromGE(bot: Player, itemID: Int, amount: Int){
|
||||
GlobalScope.launch {
|
||||
val offer = GrandExchangeOffer()
|
||||
offer.itemID = itemID
|
||||
offer.sell = false
|
||||
offer.offeredValue = checkPriceOverrides(itemID) ?: ItemDefinition.forId(itemID).value
|
||||
offer.amount = amount
|
||||
offer.player = bot
|
||||
//GrandExchange.dispatch(bot, offer)
|
||||
AIRepository.addOffer(bot, offer)
|
||||
var bought: Boolean = false
|
||||
val latch = CountDownLatch(1)
|
||||
bot.pulseManager.run(object : Pulse(5) {
|
||||
override fun pulse(): Boolean {
|
||||
bought = offer.completedAmount == offer.amount
|
||||
latch.countDown()
|
||||
return true
|
||||
}
|
||||
})
|
||||
latch.await()
|
||||
if(bought){
|
||||
bot.bank.add(Item(offer.itemID, offer.completedAmount))
|
||||
bot.bank.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to allow a bot to withdraw items from its bank.
|
||||
* @param itemID the item to withdraw.
|
||||
* @param amount the amount to withdraw.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun withdraw(itemID: Int, amount: Int){
|
||||
var item: Item? = null
|
||||
if(bot.bank.containsItem(Item(itemID, amount))){
|
||||
item = Item(itemID, amount)
|
||||
} else {
|
||||
item = Item(itemID, bot.bank.getAmount(itemID))
|
||||
}
|
||||
if(item.amount == 0) return
|
||||
if(!bot.inventory.hasSpaceFor(item)){
|
||||
item.amount = bot.inventory.getMaximumAdd(item)
|
||||
}
|
||||
bot.bank.remove(item)
|
||||
bot.inventory.add(item)
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to equip list of items and set stats
|
||||
* Useful for starting a bot up
|
||||
* @param items the list of items to equip
|
||||
* @author dginovker
|
||||
*/
|
||||
fun equipAndSetStats(items: List<Item>?){
|
||||
if (items == null) return
|
||||
for(item in items){
|
||||
equipAndSetStats(item)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to equip a single item and set stats
|
||||
* Useful for starting a bot up
|
||||
* @param item the item to equip
|
||||
* @author dginovker
|
||||
*/
|
||||
fun equipAndSetStats(item: Item){
|
||||
val configs = item.definition.handlers
|
||||
val slot = configs["equipment_slot"] ?: return
|
||||
bot.equipment.add(item, slot as Int,
|
||||
false,false)
|
||||
val reqs = configs["requirements"]
|
||||
if(reqs != null)
|
||||
for(req in configs["requirements"] as HashMap<Int,Int>)
|
||||
bot.skills.setStaticLevel(req.key, req.value)
|
||||
bot.skills.updateCombatLevel()
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to load appearance and equipment from JSON
|
||||
* Useful for starting a bot up
|
||||
* @param json the JSON object to load from (dumped via ::dumpappearance)
|
||||
* @author dginovker
|
||||
*/
|
||||
fun loadAppearanceAndEquipment(json: JSONObject?) {
|
||||
if (json == null) return
|
||||
bot.equipment.clear()
|
||||
bot.appearance.parse(json["appearance"] as JSONObject)
|
||||
val equipment = json["equipment"] as JSONArray
|
||||
bot.equipment.parse(equipment)
|
||||
bot.appearance.sync()
|
||||
for (i in 0 until bot.equipment.capacity()) {
|
||||
val item = bot.equipment.get(i)
|
||||
if (item != null) {
|
||||
equipAndSetStats(item)
|
||||
}
|
||||
}
|
||||
// Set all combat stats to a function of the highest combat stat(otherwise you end up with lopsided stats)
|
||||
val highestCombatSkill = bot.skills.getStaticLevel(bot.skills.highestCombatSkillId)
|
||||
for (i in 0 until 7) {
|
||||
bot.skills.setStaticLevel(i, max((highestCombatSkill * 0.75).toInt(), bot.skills.getStaticLevel(i)))
|
||||
}
|
||||
bot.skills.updateCombatLevel()
|
||||
}
|
||||
|
||||
fun getOverlay(): BottingOverlay {
|
||||
return BottingOverlay(bot)
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to check for price overrides.
|
||||
* @param id the id to check for overrides for.
|
||||
* @author Ceikry
|
||||
*/
|
||||
fun checkPriceOverrides(id: Int): Int?{
|
||||
return when(id){
|
||||
else -> itemDefinition(id).getConfiguration(ItemConfigParser.GE_PRICE)
|
||||
}
|
||||
}
|
||||
|
||||
class BottingOverlay(val player: Player){
|
||||
fun init(){
|
||||
player.interfaceManager.openOverlay(Component(195))
|
||||
player.packetDispatch.sendInterfaceConfig(195,5,true)
|
||||
}
|
||||
fun setTitle(title: String){
|
||||
player.packetDispatch.sendString(colorize("%B$title"),195,7)
|
||||
}
|
||||
fun setTaskLabel(label: String){
|
||||
player.packetDispatch.sendString(colorize("%B$label"),195,8)
|
||||
}
|
||||
fun setAmount(amount: Int){
|
||||
player.packetDispatch.sendString(colorize("%B$amount"),195,9)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package core.game.bots
|
||||
|
||||
annotation class ScriptDescription(vararg val value: String)
|
||||
@@ -0,0 +1,3 @@
|
||||
package core.game.bots
|
||||
|
||||
annotation class ScriptIdentifier(val value: String)
|
||||
@@ -0,0 +1,3 @@
|
||||
package core.game.bots
|
||||
|
||||
annotation class ScriptName(val value: String)
|
||||
@@ -0,0 +1,44 @@
|
||||
package core.game.bots
|
||||
|
||||
import core.game.node.item.Item
|
||||
import core.game.world.map.Location
|
||||
|
||||
class SkillingBotAssembler {
|
||||
fun produce(type: Wealth, loc: Location): AIPlayer {
|
||||
return assembleBot(AIPlayer(loc),type)
|
||||
}
|
||||
|
||||
fun assembleBot(bot: AIPlayer, type: Wealth): AIPlayer {
|
||||
return when(type){
|
||||
Wealth.POOR -> equipSet(bot,POORSETS.random())
|
||||
Wealth.AVERAGE -> equipSet(bot,AVGSETS.random())
|
||||
Wealth.RICH -> equipSet(bot,RICHSETS.random())
|
||||
}
|
||||
}
|
||||
|
||||
fun equipSet(bot: AIPlayer, set: List<Int>): AIPlayer {
|
||||
for(i in set){
|
||||
val item = Item(i)
|
||||
val configs = item.definition.handlers
|
||||
val slot = configs["equipment_slot"] ?: continue
|
||||
bot.equipment.add(item, slot as Int,
|
||||
false,false)
|
||||
val reqs = configs["requirements"]
|
||||
if(reqs != null)
|
||||
for(req in configs["requirements"] as HashMap<Int,Int>)
|
||||
bot.skills.setStaticLevel(req.key, req.value)
|
||||
}
|
||||
bot.skills.updateCombatLevel()
|
||||
return bot
|
||||
}
|
||||
|
||||
enum class Wealth {
|
||||
POOR,
|
||||
AVERAGE,
|
||||
RICH
|
||||
}
|
||||
|
||||
val POORSETS = arrayOf(listOf(542,544), listOf(581), listOf(6654,6655,6656), listOf(6654,6656), listOf(636,646), listOf(638,648), listOf(), listOf(), listOf())
|
||||
val AVGSETS = arrayOf(listOf(2649,342,344), listOf(2651,542,544), listOf(6654,6655,6656), listOf(6139,6141), listOf(9923,9924,9925), listOf(10400,10402,2649), listOf(10404,10406), listOf(12971,12978))
|
||||
val RICHSETS = arrayOf(listOf(10330,10332,2649), listOf(12873,12880,1046), listOf(13858,13861,13864), listOf(13887,13893), listOf(3481,3483), listOf(2653,2655), listOf(2661,2663), listOf(2591,2593), listOf(14490,14492))
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package core.game.component;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
|
||||
/**
|
||||
* An event called when the interface gets closed.
|
||||
* @author Emperor
|
||||
*/
|
||||
public interface CloseEvent {
|
||||
|
||||
/**
|
||||
* Called when the interface gets closed.
|
||||
* @param player The player.
|
||||
* @param c The component.
|
||||
* @return {@code True} if successful, {@code false} if the component should
|
||||
* remain open.
|
||||
*/
|
||||
boolean close(Player player, Component c);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package core.game.component;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.entity.player.link.InterfaceManager;
|
||||
import core.net.packet.PacketRepository;
|
||||
import core.net.packet.context.InterfaceContext;
|
||||
import core.net.packet.out.Interface;
|
||||
import core.game.interaction.InterfaceListeners;
|
||||
|
||||
/**
|
||||
* Represents a component.
|
||||
* @author Emperor
|
||||
*
|
||||
*/
|
||||
public class Component {
|
||||
|
||||
/**
|
||||
* The component id.
|
||||
*/
|
||||
protected int id;
|
||||
|
||||
/**
|
||||
* The component definitions.
|
||||
*/
|
||||
protected final ComponentDefinition definition;
|
||||
|
||||
/**
|
||||
* The close event.
|
||||
*/
|
||||
protected CloseEvent closeEvent;
|
||||
|
||||
/**
|
||||
* The component plugin.
|
||||
*/
|
||||
protected ComponentPlugin plugin;
|
||||
|
||||
/**
|
||||
* If the component is hidden.
|
||||
*/
|
||||
private boolean hidden;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Component} {@code Object}.
|
||||
* @param id The component id.
|
||||
*/
|
||||
public Component(int id) {
|
||||
this.id = id;
|
||||
this.definition = ComponentDefinition.forId(id);
|
||||
this.plugin = definition.getPlugin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the component.
|
||||
*/
|
||||
public void open(Player player) {
|
||||
InterfaceManager manager = player.getInterfaceManager();
|
||||
InterfaceListeners.runOpen(player,this);
|
||||
if (definition == null) {
|
||||
PacketRepository.send(Interface.class, new InterfaceContext(player, manager.getWindowPaneId(), manager.getDefaultChildId(), getId(), false));
|
||||
if (plugin != null) {
|
||||
plugin.open(player, this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (definition.getType() == InterfaceType.WINDOW_PANE) {
|
||||
return;
|
||||
}
|
||||
if (definition.getType() == InterfaceType.TAB) {
|
||||
PacketRepository.send(Interface.class, new InterfaceContext(player, definition.getWindowPaneId(manager.isResizable()), definition.getChildId(manager.isResizable()) + definition.getTabIndex(), getId(), definition.isWalkable()));
|
||||
if (plugin != null) {
|
||||
plugin.open(player, this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
PacketRepository.send(Interface.class, new InterfaceContext(player, definition.getWindowPaneId(manager.isResizable()), definition.getChildId(manager.isResizable()), getId(), definition.isWalkable()));
|
||||
if (plugin != null) {
|
||||
plugin.open(player, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the component.
|
||||
* @param player The player.
|
||||
* @return {@code True} if the component can be closed.
|
||||
*/
|
||||
public boolean close(Player player) {
|
||||
return (closeEvent == null || closeEvent.close(player, this)) && InterfaceListeners.runClose(player, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the id.
|
||||
* @return The id.
|
||||
*/
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the definition.
|
||||
* @return The definition.
|
||||
*/
|
||||
public ComponentDefinition getDefinition() {
|
||||
return definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the closeEvent.
|
||||
* @return The closeEvent.
|
||||
*/
|
||||
public CloseEvent getCloseEvent() {
|
||||
return closeEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the closeEvent.
|
||||
* @param closeEvent The closeEvent to set.
|
||||
*/
|
||||
public Component setCloseEvent(CloseEvent closeEvent) {
|
||||
this.closeEvent = closeEvent;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the component unclosable.
|
||||
* @param c The component.
|
||||
*/
|
||||
public static void setUnclosable(Player p, Component c) {
|
||||
p.setAttribute("close_c_", true);
|
||||
c.setCloseEvent(new CloseEvent() {
|
||||
@Override
|
||||
public boolean close(Player player, Component c) {
|
||||
return !player.getAttribute("close_c_", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the plugin.
|
||||
* @param plugin the plugin.
|
||||
*/
|
||||
public void setPlugin(ComponentPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the component plugin.
|
||||
* @return the plugin.
|
||||
*/
|
||||
public ComponentPlugin getPlugin() {
|
||||
if (plugin == null) {
|
||||
ComponentPlugin p = ComponentDefinition.forId(getId()).getPlugin();
|
||||
if ((plugin = p) != null) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hidden value.
|
||||
* @return The hidden.
|
||||
*/
|
||||
public boolean isHidden() {
|
||||
return hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the hidden value.
|
||||
* @param hidden The hidden to set.
|
||||
*/
|
||||
public void setHidden(boolean hidden) {
|
||||
this.hidden = hidden;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package core.game.component;
|
||||
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents the component definitions.
|
||||
* @author Emperor
|
||||
*
|
||||
*/
|
||||
public final class ComponentDefinition {
|
||||
|
||||
/**
|
||||
* The component definitions mapping.
|
||||
*/
|
||||
private static final Map<Integer, ComponentDefinition> DEFINITIONS = new HashMap<Integer, ComponentDefinition>();
|
||||
|
||||
/**
|
||||
* The interface type.
|
||||
*/
|
||||
private InterfaceType type = InterfaceType.DEFAULT;
|
||||
|
||||
/**
|
||||
* The interface context.
|
||||
*/
|
||||
private boolean walkable;
|
||||
|
||||
/**
|
||||
* The tab index.
|
||||
*/
|
||||
private int tabIndex = -1;
|
||||
|
||||
/**
|
||||
* Represents the plugin handler.
|
||||
*/
|
||||
private ComponentPlugin plugin;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code ComponentDefinition} {@code Object}.
|
||||
*/
|
||||
public ComponentDefinition() {
|
||||
/*
|
||||
* empty.
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the definition values from a result set.
|
||||
* @throws SQLException The exception if thrown.
|
||||
*/
|
||||
public ComponentDefinition parse(String type, String walkable, String tabIndex){
|
||||
setType(InterfaceType.values()[Integer.parseInt(type)]);
|
||||
setWalkable(Boolean.parseBoolean(walkable));
|
||||
setTabIndex(Integer.parseInt(tabIndex));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the component definitions for the component id.
|
||||
* @param componentId The component id.
|
||||
* @return The component definitions.
|
||||
*/
|
||||
public static ComponentDefinition forId(int componentId) {
|
||||
ComponentDefinition def = DEFINITIONS.get(componentId);
|
||||
if (def == null) {
|
||||
DEFINITIONS.put(componentId, def = new ComponentDefinition());
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a plugin to a definition.
|
||||
* @param id the id.
|
||||
* @param plugin the plugin.
|
||||
*/
|
||||
public static void put(int id, ComponentPlugin plugin) {
|
||||
ComponentDefinition.forId(id).setPlugin(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the definitions mapping.
|
||||
* @return The definitions mapping.
|
||||
*/
|
||||
public static Map<Integer, ComponentDefinition> getDefinitions() {
|
||||
return DEFINITIONS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin.
|
||||
* @return The plugin.
|
||||
*/
|
||||
public ComponentPlugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the plugin.
|
||||
* @param plugin The plugin to set.
|
||||
*/
|
||||
public void setPlugin(ComponentPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the window pane id.
|
||||
* @param resizable If the player is using resizable mode.
|
||||
* @return The window pane id.
|
||||
*/
|
||||
public int getWindowPaneId(boolean resizable) {
|
||||
return resizable ? type.getResizablePaneId() : type.getFixedPaneId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the child id.
|
||||
* @param resizable If the player is using resizable mode.
|
||||
* @return The child id.
|
||||
*/
|
||||
public int getChildId(boolean resizable) {
|
||||
return resizable ? type.getResizableChildId() : type.getFixedChildId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type.
|
||||
* @return the type
|
||||
*/
|
||||
public InterfaceType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the batype.
|
||||
* @param type the type to set.
|
||||
*/
|
||||
public void setType(InterfaceType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the walkable.
|
||||
* @return the walkable
|
||||
*/
|
||||
public boolean isWalkable() {
|
||||
return walkable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bawalkable.
|
||||
* @param walkable the walkable to set.
|
||||
*/
|
||||
public void setWalkable(boolean walkable) {
|
||||
this.walkable = walkable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tabIndex.
|
||||
* @return the tabIndex
|
||||
*/
|
||||
public int getTabIndex() {
|
||||
return tabIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the batabIndex.
|
||||
* @param tabIndex the tabIndex to set.
|
||||
*/
|
||||
public void setTabIndex(int tabIndex) {
|
||||
this.tabIndex = tabIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ComponentDefinition [type=" + type + ", walkable=" + walkable + ", tabIndex=" + tabIndex + ", plugin=" + plugin + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package core.game.component;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Represents the plugin used to handle a component reward.
|
||||
* @author Vexia
|
||||
*/
|
||||
public abstract class ComponentPlugin implements Plugin<Object> {
|
||||
|
||||
/**
|
||||
* Handles the interface interaction.
|
||||
* @param player The player.
|
||||
* @param component The component.
|
||||
* @param opcode The opcode.
|
||||
* @param slot The slot.
|
||||
* @param itemId The item id.
|
||||
* @return {@code True} if succesfully handled.
|
||||
*/
|
||||
public abstract boolean handle(final Player player, Component component, final int opcode, final int button, int slot, int itemId);
|
||||
|
||||
/**
|
||||
* Called when this component opens.
|
||||
* @param player The player
|
||||
* @param component The component opening.
|
||||
*/
|
||||
public void open(Player player, Component component) {}
|
||||
|
||||
@Override
|
||||
public Object fireEvent(String identifier, Object... args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package core.game.component;
|
||||
|
||||
|
||||
import org.rs09.consts.Components;
|
||||
|
||||
/**
|
||||
* Represents an interface type.
|
||||
* @author Emperor
|
||||
*
|
||||
*/
|
||||
public enum InterfaceType {
|
||||
|
||||
/**
|
||||
* Default interface.
|
||||
*/
|
||||
DEFAULT(Components.TOPLEVEL_548, Components.TOPLEVEL_FULLSCREEN_746, 11, 6),
|
||||
|
||||
/**
|
||||
* Walkable interface.
|
||||
*/
|
||||
OVERLAY(Components.TOPLEVEL_548, Components.TOPLEVEL_FULLSCREEN_746, 4, 5),
|
||||
|
||||
/**
|
||||
* A tab interface.
|
||||
*/
|
||||
TAB(Components.TOPLEVEL_548, Components.TOPLEVEL_FULLSCREEN_746, 83, 93),
|
||||
|
||||
/**
|
||||
* The only tab to be shown (when this type is opened).
|
||||
*/
|
||||
SINGLE_TAB(Components.TOPLEVEL_548, Components.TOPLEVEL_FULLSCREEN_746, 80, 76),
|
||||
|
||||
/**
|
||||
* Chatbox dialogue interface.
|
||||
*/
|
||||
DIALOGUE(Components.CHATTOP_752, Components.CHATTOP_752, 12, 12),
|
||||
|
||||
/**
|
||||
* A window pane.
|
||||
*/
|
||||
WINDOW_PANE(Components.TOPLEVEL_548, Components.TOPLEVEL_FULLSCREEN_746, 0, 0),
|
||||
|
||||
/**
|
||||
* Client script chatbox interface.
|
||||
*/
|
||||
CS_CHATBOX(Components.CHATTOP_752, Components.CHATTOP_752, 6, 6),
|
||||
|
||||
/**
|
||||
* Chatbox interface.
|
||||
*/
|
||||
CHATBOX(Components.CHATTOP_752, Components.CHATTOP_752, 8, 8),
|
||||
|
||||
/**
|
||||
* Wilderness overlay
|
||||
*/
|
||||
WILDERNESS_OVERLAY(Components.TOPLEVEL_548, Components.TOPLEVEL_FULLSCREEN_746, 11, 3);
|
||||
|
||||
/**
|
||||
* The fixed window pane id.
|
||||
*/
|
||||
private final int fixedPaneId;
|
||||
|
||||
/**
|
||||
* The resizable window pane id.
|
||||
*/
|
||||
private final int resizablePaneId;
|
||||
|
||||
/**
|
||||
* The fixed child id.
|
||||
*/
|
||||
private final int fixedChildId;
|
||||
|
||||
/**
|
||||
* The resizable child id.
|
||||
*/
|
||||
private final int resizableChildId;
|
||||
|
||||
/**
|
||||
* Constructs a new {@Code InterfaceType} {@Code Object}
|
||||
* @param fixedPaneId The fixed window pane id.
|
||||
* @param resizablePaneId The resizable window pane id.
|
||||
* @param fixedChildId The fixed child id.
|
||||
* @param resizableChildId The resizable child id.
|
||||
*/
|
||||
private InterfaceType(int fixedPaneId, int resizablePaneId, int fixedChildId, int resizableChildId) {
|
||||
this.fixedPaneId = fixedPaneId;
|
||||
this.resizablePaneId = resizablePaneId;
|
||||
this.fixedChildId = fixedChildId;
|
||||
this.resizableChildId = resizableChildId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fixedPaneId.
|
||||
* @return the fixedPaneId
|
||||
*/
|
||||
public int getFixedPaneId() {
|
||||
return fixedPaneId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the resizablePaneId.
|
||||
* @return the resizablePaneId
|
||||
*/
|
||||
public int getResizablePaneId() {
|
||||
return resizablePaneId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fixedChildId.
|
||||
* @return the fixedChildId
|
||||
*/
|
||||
public int getFixedChildId() {
|
||||
return fixedChildId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the resizableChildId.
|
||||
* @return the resizableChildId
|
||||
*/
|
||||
public int getResizableChildId() {
|
||||
return resizableChildId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package core.game.consumable;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
|
||||
public class BarbarianMix extends Potion {
|
||||
|
||||
public BarbarianMix(int[] ids, ConsumableEffect effect, String... messages) {
|
||||
super(ids, effect, messages);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendDefaultMessages(Player player, Item item) {
|
||||
player.getPacketDispatch().sendMessage("You drink the lumpy potion.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package core.game.consumable;
|
||||
|
||||
import content.data.consumables.Consumables;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
|
||||
/**
|
||||
* Represents any cake item that is made of three even slices.
|
||||
*/
|
||||
public class Cake extends Food {
|
||||
|
||||
public Cake(int[] ids, ConsumableEffect effect, String... messages) {
|
||||
super(ids, effect, messages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(Item item, Player player) {
|
||||
executeConsumptionActions(player);
|
||||
final int nextItemId = getNextItemId(item.getId());
|
||||
if (nextItemId != -1) {
|
||||
player.getInventory().replace(new Item(nextItemId), item.getSlot());
|
||||
} else {
|
||||
player.getInventory().remove(item);
|
||||
}
|
||||
final int initialLifePoints = player.getSkills().getLifepoints();
|
||||
Consumables.getConsumableById(item.getId()).effect.activate(player);
|
||||
sendMessages(player, initialLifePoints, item, messages);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendMessages(Player player, int initialLifePoints, Item item, String[] messages) {
|
||||
if (messages.length == 0) {
|
||||
sendDefaultMessages(player, item);
|
||||
} else {
|
||||
sendCustomMessages(player, messages, item.getId());
|
||||
}
|
||||
sendHealingMessage(player, initialLifePoints);
|
||||
}
|
||||
|
||||
private void sendCustomMessages(final Player player, final String[] messages, int itemId) {
|
||||
int i = 0;
|
||||
while (ids[i] != itemId) {
|
||||
i++;
|
||||
}
|
||||
player.getPacketDispatch().sendMessage(messages[i]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package core.game.consumable;
|
||||
|
||||
import content.data.consumables.Consumables;
|
||||
import core.api.Container;
|
||||
import static core.api.ContentAPIKt.*;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
import core.game.world.update.flag.context.Animation;
|
||||
import core.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Represents any item that has a consumption option such as 'Eat' or 'Drink'.
|
||||
*/
|
||||
public abstract class Consumable implements Plugin<Object> {
|
||||
|
||||
/**
|
||||
* Represents the item IDs of all the variants of a consumable where the last one is often the empty container, if it has any.
|
||||
*/
|
||||
protected final int[] ids;
|
||||
|
||||
/**
|
||||
* Represents the effect to apply on the player once the item is consumed.
|
||||
*/
|
||||
protected final ConsumableEffect effect;
|
||||
|
||||
/**
|
||||
* Represents the messages to send to the player when it consumes the item.
|
||||
*/
|
||||
protected final String[] messages;
|
||||
|
||||
/**
|
||||
* Represents the animation that the player will execute when consuming the item.
|
||||
*/
|
||||
protected Animation animation = null;
|
||||
|
||||
public Consumable(final int[] ids, final ConsumableEffect effect, final String... messages) {
|
||||
this.ids = ids;
|
||||
this.effect = effect;
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
public Consumable(final int[] ids, final ConsumableEffect effect, final Animation animation, final String... messages) {
|
||||
this.ids = ids;
|
||||
this.effect = effect;
|
||||
this.animation = animation;
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
public void consume(final Item item, final Player player) {
|
||||
executeConsumptionActions(player);
|
||||
final int nextItemId = getNextItemId(item.getId());
|
||||
|
||||
if(item.getAmount() > 1){
|
||||
removeItem(player, item.getId(), Container.INVENTORY);
|
||||
} else removeItem(player, item, Container.INVENTORY);
|
||||
|
||||
if (nextItemId != -1) {
|
||||
addItem(player, nextItemId, 1, Container.INVENTORY);
|
||||
}
|
||||
final int initialLifePoints = player.getSkills().getLifepoints();
|
||||
Consumables.getConsumableById(item.getId()).effect.activate(player);
|
||||
sendMessages(player, initialLifePoints, item, messages);
|
||||
}
|
||||
|
||||
protected void sendMessages(final Player player, final int initialLifePoints, final Item item, String[] messages) {
|
||||
if (messages.length == 0) {
|
||||
sendDefaultMessages(player, item);
|
||||
sendHealingMessage(player, initialLifePoints);
|
||||
} else {
|
||||
sendCustomMessages(player, messages);
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendHealingMessage(final Player player, final int initialLifePoints) {
|
||||
if (player.getSkills().getLifepoints() > initialLifePoints) {
|
||||
player.getPacketDispatch().sendMessage("It heals some health.");
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendCustomMessages(final Player player, final String[] messages) {
|
||||
for (String message : messages) {
|
||||
player.getPacketDispatch().sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void sendDefaultMessages(final Player player, final Item item);
|
||||
|
||||
protected abstract void executeConsumptionActions(Player player);
|
||||
|
||||
protected int getNextItemId(final int currentConsumableId) {
|
||||
for (int i = 0; i < ids.length; i++) {
|
||||
if (ids[i] == currentConsumableId && i != ids.length - 1) {
|
||||
return ids[i + 1];
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public String getFormattedName(Item item) {
|
||||
return item.getName().replace("(4)", "").replace("(3)", "").replace("(2)", "").replace("(1)", "").trim().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin<Object> newInstance(Object arg) throws Throwable {
|
||||
Consumables.add(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fireEvent(String identifier, Object... args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getHealthEffectValue(Player player) {
|
||||
return effect.getHealthEffectValue(player);
|
||||
}
|
||||
|
||||
public ConsumableEffect getEffect() {
|
||||
return effect;
|
||||
}
|
||||
|
||||
public int[] getIds() {
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package core.game.consumable;
|
||||
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
|
||||
public abstract class ConsumableEffect {
|
||||
public abstract void activate(Player p);
|
||||
|
||||
public int getHealthEffectValue(Player player) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package core.game.consumable;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.entity.player.link.audio.Audio;
|
||||
import core.game.node.item.Item;
|
||||
import core.game.world.update.flag.context.Animation;
|
||||
|
||||
public class Drink extends Consumable {
|
||||
|
||||
public static final Audio SOUND = new Audio(4580, 1, 1);
|
||||
|
||||
public Drink(int[] ids, ConsumableEffect effect, String... messages) {
|
||||
super(ids, effect, messages);
|
||||
animation = new Animation(1327);
|
||||
}
|
||||
|
||||
public Drink(int[] ids, ConsumableEffect effect, Animation animation, String... messages) {
|
||||
super(ids, effect, animation, messages);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendDefaultMessages(Player player, Item item) {
|
||||
player.getPacketDispatch().sendMessage("You drink the " + getFormattedName(item) + ".");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void executeConsumptionActions(Player player) {
|
||||
player.animate(animation);
|
||||
player.getAudioManager().send(SOUND);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormattedName(Item item) {
|
||||
return item.getName().replace("(4)", "").replace("(3)", "").replace("(2)", "").replace("(1)", "").replace("(m4)", "").replace("(m3)", "").replace("(m2)", "").replace("(m1)", "").replace("(m)", "").trim().toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package core.game.consumable;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
|
||||
/**
|
||||
* Represents a special type of consumable that cannot be consumed unless it is transformed into another item.
|
||||
* During its 'consumption', only a message is sent to the player.
|
||||
*/
|
||||
public class FakeConsumable extends Consumable {
|
||||
|
||||
public FakeConsumable(final int id, final String[] messages) {
|
||||
super(new int[] {id}, null, messages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(Item item, Player player) {
|
||||
sendDefaultMessages(player, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendDefaultMessages(Player player, Item item) {
|
||||
for (String message : messages) {
|
||||
player.getPacketDispatch().sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void executeConsumptionActions(Player player) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package core.game.consumable;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.entity.player.link.audio.Audio;
|
||||
import core.game.node.item.Item;
|
||||
import core.game.world.update.flag.context.Animation;
|
||||
|
||||
public class Food extends Consumable {
|
||||
|
||||
public static final Audio SOUND = new Audio(2393, 1, 1);
|
||||
|
||||
public Food(final int[] ids, final ConsumableEffect effect, final String... messages) {
|
||||
super(ids, effect, messages);
|
||||
animation = new Animation(829);
|
||||
}
|
||||
|
||||
public Food(int[] ids, ConsumableEffect effect, Animation animation, String... messages) {
|
||||
super(ids, effect, animation, messages);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendDefaultMessages(Player player, Item item) {
|
||||
player.getPacketDispatch().sendMessage("You eat the " + getFormattedName(item) + ".");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void executeConsumptionActions(Player player) {
|
||||
player.animate(animation);
|
||||
playEatingSound(player);
|
||||
}
|
||||
|
||||
private void playEatingSound(Player player) {
|
||||
player.getAudioManager().send(SOUND);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package core.game.consumable;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
|
||||
/**
|
||||
* Represents any food that is consumed in two parts such as pies and pizzas.
|
||||
*/
|
||||
public class HalfableFood extends Food {
|
||||
|
||||
public HalfableFood(int[] ids, ConsumableEffect effect, String... messages) {
|
||||
super(ids, effect, messages);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendDefaultMessages(Player player, Item item) {
|
||||
if (item.getId() == ids[0]) {
|
||||
player.getPacketDispatch().sendMessage("You eat half the "+ getFormattedName(item) + ".");
|
||||
} else if (item.getId() == ids[1]) {
|
||||
player.getPacketDispatch().sendMessage("You eat the remaining " + getFormattedName(item) + ".");
|
||||
} else {
|
||||
super.sendDefaultMessages(player, item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormattedName(Item item) {
|
||||
return item.getName().replace("1/2", "").replace("Half an", "").replace("Half a", "").trim().toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package core.game.consumable;
|
||||
|
||||
import content.data.consumables.Consumables;
|
||||
import content.region.kandarin.barcrawl.BarcrawlManager;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.entity.player.link.audio.Audio;
|
||||
import core.game.node.item.Item;
|
||||
|
||||
public class Potion extends Drink {
|
||||
|
||||
private static final int VIAL = 229;
|
||||
|
||||
private static final Audio SOUND = new Audio(2401, 1, 1);
|
||||
|
||||
public Potion(final int[] ids, final ConsumableEffect effect, final String... messages) {
|
||||
super(ids, effect, messages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(Item item, Player player) {
|
||||
executeConsumptionActions(player);
|
||||
final int nextItemId = getNextItemId(item.getId());
|
||||
if (nextItemId != -1) {
|
||||
player.getInventory().replace(new Item(nextItemId), item.getSlot());
|
||||
} else {
|
||||
if (BarcrawlManager.getInstance(player).isFinished()) {
|
||||
player.getInventory().remove(item);
|
||||
} else {
|
||||
player.getInventory().replace(new Item(VIAL), item.getSlot());
|
||||
}
|
||||
}
|
||||
final int initialLifePoints = player.getSkills().getLifepoints();
|
||||
Consumables.getConsumableById(item.getId()).effect.activate(player);
|
||||
if (messages.length == 0) {
|
||||
sendDefaultMessages(player, item);
|
||||
} else {
|
||||
sendCustomMessages(player, messages);
|
||||
}
|
||||
sendHealingMessage(player, initialLifePoints);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void executeConsumptionActions(Player player) {
|
||||
player.animate(animation);
|
||||
player.getAudioManager().send(SOUND);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendDefaultMessages(Player player, Item item) {
|
||||
int consumedDoses = 1;
|
||||
int i = 0;
|
||||
while (ids[i] != item.getId()) {
|
||||
consumedDoses++;
|
||||
i++;
|
||||
}
|
||||
final int dosesLeft = ids.length - consumedDoses;
|
||||
player.getPacketDispatch().sendMessage("You drink some of your " + getFormattedName(item) + ".");
|
||||
if (dosesLeft > 1) {
|
||||
player.getPacketDispatch().sendMessage("You have " + dosesLeft + " doses of your potion left.");
|
||||
} else if (dosesLeft == 1) {
|
||||
player.getPacketDispatch().sendMessage("You have 1 dose of your potion left.");
|
||||
} else {
|
||||
player.getPacketDispatch().sendMessage("You have finished your potion.");
|
||||
}
|
||||
}
|
||||
|
||||
public static int getDose(Item potion){
|
||||
return Integer.parseInt(potion.getName().replaceAll("[^\\d.]",""));
|
||||
}
|
||||
|
||||
public int[] getIds() {
|
||||
return ids;
|
||||
}
|
||||
|
||||
public ConsumableEffect getEffect() {
|
||||
return effect;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,117 @@
|
||||
package core.game.container;
|
||||
|
||||
import core.game.node.item.Item;
|
||||
|
||||
/**
|
||||
* Represents a container event.
|
||||
* @author Emperor
|
||||
*/
|
||||
public final class ContainerEvent {
|
||||
|
||||
/**
|
||||
* Represents a null item.
|
||||
*/
|
||||
public static final Item NULL_ITEM = new Item(0, 0);
|
||||
|
||||
/**
|
||||
* The array of changed items.
|
||||
*/
|
||||
private final Item[] items;
|
||||
|
||||
/**
|
||||
* Clears the container.
|
||||
*/
|
||||
private boolean clear;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code ContainerEvent} {@code Object}.
|
||||
* @param size The container size.
|
||||
*/
|
||||
public ContainerEvent(int size) {
|
||||
this.items = new Item[size];
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags a null item on the given slot.
|
||||
* @param slot The slot.
|
||||
*/
|
||||
public void flagNull(int slot) {
|
||||
items[slot] = NULL_ITEM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags an item on the given slot.
|
||||
* @param slot The slot.
|
||||
* @param item The item.
|
||||
*/
|
||||
public void flag(int slot, Item item) {
|
||||
items[slot] = item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of item slots changed.
|
||||
* @return The amount of item slots that have changed.
|
||||
*/
|
||||
public int getChangeCount() {
|
||||
int count = 0;
|
||||
for (Item item : items) {
|
||||
if (item != null) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the updated slots.
|
||||
* @return The slots array.
|
||||
*/
|
||||
public int[] getSlots() {
|
||||
int size = 0;
|
||||
int[] slots = new int[items.length];
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
if (items[i] != null) {
|
||||
slots[size++] = i;
|
||||
}
|
||||
}
|
||||
int[] slot = new int[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
slot[i] = slots[i];
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the items.
|
||||
* @return The items.
|
||||
*/
|
||||
public Item[] getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags an empty container.
|
||||
*/
|
||||
public void flagEmpty() {
|
||||
this.clear = true;
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
items[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the clear.
|
||||
* @return The clear.
|
||||
*/
|
||||
public boolean isClear() {
|
||||
return clear;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the clear flag.
|
||||
* @param clear The container is cleared.
|
||||
*/
|
||||
public void setClear(boolean clear) {
|
||||
this.clear = clear;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package core.game.container;
|
||||
|
||||
/**
|
||||
* Represents a container listener.
|
||||
* @author Emperor
|
||||
*/
|
||||
public interface ContainerListener {
|
||||
|
||||
/**
|
||||
* Updates the changed item slots in the container.
|
||||
* @param c The container we're listening to.
|
||||
* @param event The container event.
|
||||
*/
|
||||
void update(Container c, ContainerEvent event);
|
||||
|
||||
/**
|
||||
* Updates the entire container.
|
||||
* @param c The container.
|
||||
*/
|
||||
void refresh(Container c);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package core.game.container;
|
||||
|
||||
/**
|
||||
* Represents the container types.
|
||||
* @author Emperor
|
||||
*/
|
||||
public enum ContainerType {
|
||||
|
||||
/**
|
||||
* The default container type.
|
||||
*/
|
||||
DEFAULT,
|
||||
|
||||
/**
|
||||
* If the container is used for a shop.
|
||||
*/
|
||||
SHOP,
|
||||
|
||||
/**
|
||||
* The container should always stack items.
|
||||
*/
|
||||
ALWAYS_STACK,
|
||||
|
||||
/**
|
||||
* The container should never stack items.
|
||||
*/
|
||||
NEVER_STACK;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package core.game.container;
|
||||
|
||||
/**
|
||||
* The sort type of the container.
|
||||
* @author Emperor
|
||||
*/
|
||||
public enum SortType {
|
||||
|
||||
/**
|
||||
* Sort by item id (default).
|
||||
*/
|
||||
ID,
|
||||
|
||||
/**
|
||||
* Sort by item identification hash (bank).
|
||||
*/
|
||||
HASH
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package core.game.container.access;
|
||||
|
||||
import core.api.IfaceSettingsBuilder;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
import core.net.packet.PacketRepository;
|
||||
import core.net.packet.context.ContainerContext;
|
||||
import core.net.packet.out.ContainerPacket;
|
||||
|
||||
/**
|
||||
* Generates a set of items and options on an interface.
|
||||
* @date 5/02/2013
|
||||
* @author Stacx
|
||||
*/
|
||||
public class InterfaceContainer {
|
||||
|
||||
/**
|
||||
* The client script index for set_options.
|
||||
*/
|
||||
private static final int CLIENT_SCRIPT_INDEX = 150;
|
||||
|
||||
/**
|
||||
* This index will increase each time a set is generated.
|
||||
*/
|
||||
private static int index = 600; // 93
|
||||
|
||||
/**
|
||||
* Generates a container/array of items in an interface positioned on the
|
||||
* child index.
|
||||
* @param player , the player we generate this set for
|
||||
* @param itemArray , the container/array of items we want to display
|
||||
* @param options , the right-click options we want for the items
|
||||
* @param interfaceIndex , the interface index
|
||||
* @param childIndex , the child index of the interface where we display the
|
||||
* items at.
|
||||
* @return The container key.
|
||||
*/
|
||||
private static int generate(Player player, Item[] itemArray, String[] options, int interfaceIndex, int childIndex, int x, int y, int key) {
|
||||
Object[] clientScript = new Object[options.length + 7];
|
||||
player.getPacketDispatch().sendRunScript(CLIENT_SCRIPT_INDEX, generateScriptArguments(options.length), populateScript(clientScript, options, interfaceIndex << 16 | childIndex, x, y, key));
|
||||
int settings = new IfaceSettingsBuilder().enableAllOptions().build();
|
||||
player.getPacketDispatch().sendIfaceSettings(settings, childIndex, interfaceIndex, 0, itemArray.length);
|
||||
PacketRepository.send(ContainerPacket.class, new ContainerContext(player, -1, -2, key, itemArray, itemArray.length, false));
|
||||
return increment();
|
||||
}
|
||||
|
||||
public static int generateOptions(Player player, String[] options, int interfaceIndex, int childIndex, int x, int y, int key){
|
||||
player.getPacketDispatch().sendRunScript(CLIENT_SCRIPT_INDEX, generateScriptArguments(options.length), populateScript(new Object[options.length + 7],options,interfaceIndex << 16 | childIndex, x, y, key));
|
||||
int settings = new IfaceSettingsBuilder().enableAllOptions().build();
|
||||
player.getPacketDispatch().sendIfaceSettings(settings, childIndex, interfaceIndex, 0, 28);
|
||||
return increment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates options for the interface item container.
|
||||
* @param player The player.
|
||||
* @param interfaceId The interface id.
|
||||
* @param childId The child index.
|
||||
* @param itemLength The amount of items.
|
||||
* @param options The options.
|
||||
* @return The container key.
|
||||
*/
|
||||
public static int generate(Player player, int interfaceId, int childId, int itemLength, String... options) {
|
||||
return generate(player, interfaceId, childId, itemLength, 7, 3, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates options for the interface item container.
|
||||
* @param player The player.
|
||||
* @param interfaceId The interface id.
|
||||
* @param childId The child index.
|
||||
* @param itemLength The amount of items.
|
||||
* @param x The amount of items in a row.
|
||||
* @param y The amount of item rows.
|
||||
* @param options The options.
|
||||
* @return The container key.
|
||||
*/
|
||||
public static int generate(Player player, int interfaceId, int childId, int itemLength, int x, int y, String... options) {
|
||||
int key = increment();
|
||||
Object[] clientScript = new Object[options.length + 7];
|
||||
player.getPacketDispatch().sendRunScript(CLIENT_SCRIPT_INDEX, generateScriptArguments(options.length), populateScript(clientScript, options, interfaceId << 16 | childId, x, y, key));
|
||||
int settings = new IfaceSettingsBuilder().enableAllOptions().build();
|
||||
player.getPacketDispatch().sendIfaceSettings(settings, childId, interfaceId, 0, itemLength);
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the current index.
|
||||
* @return The previous index.
|
||||
*/
|
||||
private static int increment() {
|
||||
if (index == 6999) {
|
||||
index = 600;
|
||||
}
|
||||
return index++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates an object array used as a script for the client
|
||||
* @param script , the array we want to populate
|
||||
* @param options , the right-click options for our items
|
||||
* @param hash , interfaceIndex << 16 | childIndex
|
||||
* @return script, the populated script
|
||||
*/
|
||||
private static Object[] populateScript(Object[] script, String[] options, int hash, int x, int y, int key) {
|
||||
int offset = 0;
|
||||
for (String option : options) {
|
||||
script[offset++] = option;
|
||||
}
|
||||
System.arraycopy(new Object[] { -1, 0, x, y, key, hash }, 0, script, offset, 6);
|
||||
return script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a script argument type string for the client (note: everything
|
||||
* but a "s" is integer for the run script packet)
|
||||
* @param length , the amount of options
|
||||
* @return the generated string.
|
||||
*/
|
||||
private static String generateScriptArguments(int length) {
|
||||
StringBuilder builder = new StringBuilder("IviiiI");
|
||||
while (length > 0) {
|
||||
builder.append("s");
|
||||
length--;
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default method to generate and send an item array for the client.
|
||||
* @return The container key.
|
||||
*/
|
||||
public static int generateItems(Player player, Item[] itemArray, String[] options, int interfaceIndex, int childIndex) {
|
||||
return generateItems(player, itemArray, options, interfaceIndex, childIndex, 7, 3, increment());
|
||||
}
|
||||
|
||||
/**
|
||||
* Default method to generate and send an item array for the client.
|
||||
* @return The container key.
|
||||
*/
|
||||
public static int generateItems(Player player, Item[] itemArray, String[] options, int interfaceIndex, int childIndex, int key) {
|
||||
return generateItems(player, itemArray, options, interfaceIndex, childIndex, 7, 3, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to generate the send items for the client with a specified
|
||||
* location for the items.
|
||||
* @param x , the x coordinate
|
||||
* @param y , the y coordinate
|
||||
* @return The container key.
|
||||
*/
|
||||
public static int generateItems(Player player, Item[] itemArray, String[] options, int interfaceIndex, int childIndex, int x, int y) {
|
||||
return generateItems(player, itemArray, options, interfaceIndex, childIndex, x, y, increment());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to generate the send items for the client with a specified
|
||||
* location for the items.
|
||||
* @param x , the x coordinate
|
||||
* @param y , the y coordinate
|
||||
* @return The container key.
|
||||
*/
|
||||
public static int generateItems(Player player, Item[] itemArray, String[] options, int interfaceIndex, int childIndex, int x, int y, int key) {
|
||||
return generate(player, itemArray, options, interfaceIndex, childIndex, x, y, key);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,592 @@
|
||||
package core.game.container.impl;
|
||||
|
||||
import core.api.IfaceSettingsBuilder;
|
||||
import core.game.container.access.InterfaceContainer;
|
||||
import kotlin.ranges.IntRange;
|
||||
import org.rs09.consts.Vars;
|
||||
import core.ServerConstants;
|
||||
import core.game.component.Component;
|
||||
import core.game.container.*;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.entity.player.link.IronmanMode;
|
||||
import core.game.node.item.Item;
|
||||
import core.game.system.config.ItemConfigParser;
|
||||
import core.game.world.GameWorld;
|
||||
import core.net.packet.PacketRepository;
|
||||
import core.net.packet.context.ContainerContext;
|
||||
import core.net.packet.out.ContainerPacket;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Represents the bank container.
|
||||
* @author Emperor
|
||||
*/
|
||||
public final class BankContainer extends Container {
|
||||
|
||||
/**
|
||||
* The bank container size.
|
||||
*/
|
||||
public static final int SIZE = ServerConstants.BANK_SIZE;
|
||||
|
||||
/**
|
||||
* The maximum amount of bank tabs
|
||||
*/
|
||||
public static final int TAB_SIZE = 11;
|
||||
|
||||
/**
|
||||
* The player reference.
|
||||
*/
|
||||
private Player player;
|
||||
|
||||
/**
|
||||
* The bank listener.
|
||||
*/
|
||||
private final BankListener listener;
|
||||
|
||||
/**
|
||||
* If the bank is open.
|
||||
*/
|
||||
private boolean open;
|
||||
|
||||
/**
|
||||
* The last x-amount entered.
|
||||
*/
|
||||
private int lastAmountX = 50;
|
||||
|
||||
/**
|
||||
* The current tab index.
|
||||
*/
|
||||
private int tabIndex = 10;
|
||||
|
||||
/**
|
||||
* The tab start indexes.
|
||||
*/
|
||||
private final int[] tabStartSlot = new int[TAB_SIZE];
|
||||
|
||||
/**
|
||||
* Construct a new {@code BankContainer} {@code Object}.
|
||||
* @param player The player reference.
|
||||
*/
|
||||
public BankContainer(Player player) {
|
||||
super(SIZE, ContainerType.ALWAYS_STACK, SortType.HASH);
|
||||
super.register(listener = new BankListener(player));
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to open the deposit box.
|
||||
*/
|
||||
public void openDepositBox() {
|
||||
player.getInterfaceManager().open(new Component(11)).setCloseEvent((player, c) -> {
|
||||
player.getInterfaceManager().openDefaultTabs();
|
||||
return true;
|
||||
});
|
||||
player.getInterfaceManager().removeTabs(0, 1, 2, 3, 4, 5, 6);
|
||||
refreshDepositBoxInterface();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the visual state of deposit box interface
|
||||
* forcing the client to re-draw the items
|
||||
*/
|
||||
public void refreshDepositBoxInterface()
|
||||
{
|
||||
InterfaceContainer.generateItems(
|
||||
player,
|
||||
player.getInventory().toArray(),
|
||||
new String[] {
|
||||
"Examine",
|
||||
"Deposit-X",
|
||||
"Deposit-All",
|
||||
"Deposit-10",
|
||||
"Deposit-5",
|
||||
"Deposit-1"
|
||||
}, 11, 15, 5, 7
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the bank.
|
||||
*/
|
||||
public void open() {
|
||||
if (open) {
|
||||
return;
|
||||
}
|
||||
if (player.getIronmanManager().checkRestriction(IronmanMode.ULTIMATE)) {
|
||||
return;
|
||||
}
|
||||
if (!player.getBankPinManager().isUnlocked() && !GameWorld.getSettings().isDevMode()) {
|
||||
player.getBankPinManager().openType(1);
|
||||
return;
|
||||
}
|
||||
player.getInterfaceManager().openComponent(762).setCloseEvent((player, c) -> {
|
||||
BankContainer.this.close();
|
||||
return true;
|
||||
});
|
||||
player.getInterfaceManager().openSingleTab(new Component(763));
|
||||
super.refresh();
|
||||
player.getInventory().getListeners().add(listener);
|
||||
player.getInventory().refresh();
|
||||
player.varpManager.get(1249).setVarbit(0,lastAmountX).send(player);
|
||||
int settings = new IfaceSettingsBuilder().enableOptions(new IntRange(0, 5)).enableExamine().enableSlotSwitch().build();
|
||||
player.getPacketDispatch().sendIfaceSettings(settings, 0, 763, 0, 27);
|
||||
player.getPacketDispatch().sendRunScript(1451, "");
|
||||
open = true;
|
||||
setTabConfigurations();
|
||||
sendBankSpace();
|
||||
}
|
||||
|
||||
public void open(Player player) {
|
||||
if (open) {
|
||||
return;
|
||||
}
|
||||
if (player.getIronmanManager().checkRestriction(IronmanMode.ULTIMATE)) {
|
||||
return;
|
||||
}
|
||||
if (!player.getBankPinManager().isUnlocked() && !GameWorld.getSettings().isDevMode()) {
|
||||
player.getBankPinManager().openType(1);
|
||||
return;
|
||||
}
|
||||
player.getInterfaceManager().openComponent(762).setCloseEvent((player1, c) -> {
|
||||
BankContainer.this.close();
|
||||
return true;
|
||||
});
|
||||
refresh(listener);
|
||||
player.getInterfaceManager().openSingleTab(new Component(763));
|
||||
player.getInventory().getListeners().add(player.getBank().listener);
|
||||
player.getInventory().refresh();
|
||||
player.varpManager.get(1249).setVarbit(0,lastAmountX).send(player);
|
||||
player.getPacketDispatch().sendIfaceSettings(1278, 73, 762, 0, SIZE);
|
||||
int settings = new IfaceSettingsBuilder().enableOptions(new IntRange(0,5)).enableExamine().enableSlotSwitch().build();
|
||||
player.getPacketDispatch().sendIfaceSettings(settings, 0, 763, 0, 27);
|
||||
player.getPacketDispatch().sendRunScript(1451, "");
|
||||
open = true;
|
||||
this.player.getBank().setTabConfigurations(player);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public long save(ByteBuffer buffer) {
|
||||
buffer.putInt(lastAmountX);
|
||||
buffer.put((byte) tabStartSlot.length);
|
||||
for (int j : tabStartSlot) {
|
||||
buffer.putShort((short) j);
|
||||
}
|
||||
return super.save(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int parse(ByteBuffer buffer) {
|
||||
lastAmountX = buffer.getInt();
|
||||
int length = buffer.get() & 0xFF;
|
||||
for (int i = 0; i < length; i++) {
|
||||
tabStartSlot[i] = buffer.getShort();
|
||||
}
|
||||
return super.parse(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the bank.
|
||||
*/
|
||||
public void close() {
|
||||
open = false;
|
||||
player.getInventory().getListeners().remove(listener);
|
||||
player.getInterfaceManager().closeSingleTab();
|
||||
player.removeAttribute("search");
|
||||
player.getPacketDispatch().sendRunScript(571, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item to the bank container.
|
||||
* @param slot The item slot.
|
||||
* @param amount The amount.
|
||||
*/
|
||||
public void addItem(int slot, int amount) {
|
||||
if (slot < 0 || slot > player.getInventory().capacity() || amount < 1) {
|
||||
return;
|
||||
}
|
||||
Item item = player.getInventory().get(slot);
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!item.getDefinition().getConfiguration(ItemConfigParser.BANKABLE, true)) {
|
||||
player.sendMessage("A magical force prevents you from banking this item");
|
||||
return;
|
||||
}
|
||||
|
||||
int maximum = player.getInventory().getAmount(item);
|
||||
if (amount > maximum) {
|
||||
amount = maximum;
|
||||
}
|
||||
|
||||
item = new Item(item.getId(), amount, item.getCharge());
|
||||
boolean unnote = !item.getDefinition().isUnnoted();
|
||||
|
||||
Item add = unnote ? new Item(item.getDefinition().getNoteId(), amount, item.getCharge()) : item;
|
||||
if (unnote && !add.getDefinition().isUnnoted()) {
|
||||
add = item;
|
||||
}
|
||||
|
||||
int maxCount = super.getMaximumAdd(add);
|
||||
if (amount > maxCount) {
|
||||
add.setAmount(maxCount);
|
||||
item.setAmount(maxCount);
|
||||
if (maxCount < 1) {
|
||||
player.getPacketDispatch().sendMessage("There is not enough space left in your bank.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (player.getInventory().remove(item, slot, true)) {
|
||||
int preferredSlot = -1;
|
||||
if (tabIndex != 0 && tabIndex != 10 && !super.contains(add.getId(), 1)) {
|
||||
preferredSlot = tabStartSlot[tabIndex] + getItemsInTab(tabIndex);
|
||||
insert(freeSlot(), preferredSlot, false);
|
||||
increaseTabStartSlots(tabIndex);
|
||||
}
|
||||
super.add(add, true, preferredSlot);
|
||||
setTabConfigurations();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-opens the bank interface.
|
||||
*/
|
||||
public void reopen() {
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
player.getInterfaceManager().close();
|
||||
open();
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a item from the bank container and adds one to the inventory
|
||||
* container.
|
||||
* @param slot The slot.
|
||||
* @param amount The amount.
|
||||
*/
|
||||
public void takeItem(int slot, int amount) {
|
||||
if (slot < 0 || slot > super.capacity() || amount <= 0) {
|
||||
return;
|
||||
}
|
||||
Item item = get(slot);
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
if (amount > item.getAmount()) {
|
||||
amount = item.getAmount(); // It always stacks in the bank.
|
||||
}
|
||||
item = new Item(item.getId(), amount, item.getCharge());
|
||||
int noteId = item.getDefinition().getNoteId();
|
||||
Item add = isNoteItems() && noteId > 0 ? new Item(noteId, amount, item.getCharge()) : item;
|
||||
int maxCount = player.getInventory().getMaximumAdd(add);
|
||||
if (amount > maxCount) {
|
||||
item.setAmount(maxCount);
|
||||
add.setAmount(maxCount);
|
||||
if (maxCount < 1) {
|
||||
player.getPacketDispatch().sendMessage("Not enough space in your inventory.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (isNoteItems() && noteId < 0) {
|
||||
player.getPacketDispatch().sendMessage("This item can't be withdrawn as a note.");
|
||||
add = item;
|
||||
}
|
||||
if (super.remove(item, slot, false)) {
|
||||
player.getInventory().add(add);
|
||||
}
|
||||
int tabId = getTabByItemSlot(slot);
|
||||
if (get(slot) == null) {
|
||||
decreaseTabStartSlots(tabId);
|
||||
}
|
||||
setTabConfigurations();
|
||||
shift();
|
||||
if (player.getAttribute("search", false)) {
|
||||
reopen();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last x-amount entered.
|
||||
* @param amount The amount to set.
|
||||
*/
|
||||
public void updateLastAmountX(int amount) {
|
||||
this.lastAmountX = amount;
|
||||
player.varpManager.get(1249).setVarbit(0,amount).send(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tab the item slot is in.
|
||||
* @param itemSlot The item slot.
|
||||
* @return The tab index.
|
||||
*/
|
||||
public int getTabByItemSlot(int itemSlot) {
|
||||
int tabId = 0;
|
||||
for (int i = 0; i < tabStartSlot.length; i++) {
|
||||
if (itemSlot >= tabStartSlot[i]) {
|
||||
tabId = i;
|
||||
}
|
||||
}
|
||||
return tabId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases a tab's start slot.
|
||||
* @param startId The start id.
|
||||
*/
|
||||
public void increaseTabStartSlots(int startId) {
|
||||
for (int i = startId + 1; i < tabStartSlot.length; i++) {
|
||||
tabStartSlot[i]++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decreases a tab's start slot.
|
||||
* @param startId The start id.
|
||||
*/
|
||||
public void decreaseTabStartSlots(int startId) {
|
||||
if (startId == 10) {
|
||||
return;
|
||||
}
|
||||
for (int i = startId + 1; i < tabStartSlot.length; i++) {
|
||||
tabStartSlot[i]--;
|
||||
}
|
||||
if (getItemsInTab(startId) == 0) {
|
||||
collapseTab(startId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the array index for a tab.
|
||||
* @param tabId The tab id.
|
||||
* @return The array index.
|
||||
*/
|
||||
public static int getArrayIndex(int tabId) {
|
||||
if (tabId == 41 || tabId == 74) {
|
||||
return 10;
|
||||
}
|
||||
int base = 39;
|
||||
for (int i = 1; i < 10; i++) {
|
||||
if (tabId == base) {
|
||||
return i;
|
||||
}
|
||||
base -= 2;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the bank space values on the interface.
|
||||
*/
|
||||
public void sendBankSpace() {
|
||||
player.getPacketDispatch().sendString(Integer.toString(capacity() - freeSlots()), 762, 97);
|
||||
player.getPacketDispatch().sendString(Integer.toString(capacity()), 762, 98);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses a tab.
|
||||
* @param tabId The tab index.
|
||||
*/
|
||||
public void collapseTab(int tabId) {
|
||||
int size = getItemsInTab(tabId);
|
||||
Item[] tempTabItems = new Item[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
tempTabItems[i] = get(tabStartSlot[tabId] + i);
|
||||
replace(null, tabStartSlot[tabId] + i, false);
|
||||
}
|
||||
shift();
|
||||
for (int i = tabId; i < tabStartSlot.length - 1; i++) {
|
||||
tabStartSlot[i] = tabStartSlot[i + 1] - size;
|
||||
}
|
||||
tabStartSlot[10] = tabStartSlot[10] - size;
|
||||
for (int i = 0; i < size; i++) {
|
||||
int slot = freeSlot();
|
||||
replace(tempTabItems[i], slot, false);
|
||||
}
|
||||
refresh(); //We only refresh once.
|
||||
setTabConfigurations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tab configs.
|
||||
*/
|
||||
public void setTabConfigurations() {
|
||||
int value = getItemsInTab(1);
|
||||
value += getItemsInTab(2) << 10;
|
||||
value += getItemsInTab(3) << 20;
|
||||
player.getConfigManager().set(1246, value);
|
||||
value = getItemsInTab(4);
|
||||
value += getItemsInTab(5) << 10;
|
||||
value += getItemsInTab(6) << 20;
|
||||
player.getConfigManager().set(1247, value);
|
||||
value = -2013265920;
|
||||
value += (134217728 * (tabIndex == 10 ? 0 : tabIndex));
|
||||
value += getItemsInTab(7);
|
||||
value += getItemsInTab(8) << 10;
|
||||
player.getConfigManager().set(1248, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tab configs.
|
||||
*/
|
||||
public void setTabConfigurations(Player player) {
|
||||
int value = getItemsInTab(1);
|
||||
value += getItemsInTab(2) << 10;
|
||||
value += getItemsInTab(3) << 20;
|
||||
player.getConfigManager().set(1246, value);
|
||||
value = getItemsInTab(4);
|
||||
value += getItemsInTab(5) << 10;
|
||||
value += getItemsInTab(6) << 20;
|
||||
player.getConfigManager().set(1247, value);
|
||||
value = -2013265920;
|
||||
value += (134217728 * (tabIndex == 10 ? 0 : tabIndex));
|
||||
value += getItemsInTab(7);
|
||||
value += getItemsInTab(8) << 10;
|
||||
player.getConfigManager().set(1248, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of items in one tab.
|
||||
* @param tabId The tab index.
|
||||
* @return The amount of items in this tab.
|
||||
*/
|
||||
public int getItemsInTab(int tabId) {
|
||||
return tabStartSlot[tabId + 1] - tabStartSlot[tabId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the item can be added.
|
||||
* @param item the item.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public boolean canAdd(Item item) {
|
||||
return item.getDefinition().getConfiguration(ItemConfigParser.BANKABLE, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last x-amount.
|
||||
* @return The last x-amount.
|
||||
*/
|
||||
public int getLastAmountX() {
|
||||
return lastAmountX;
|
||||
}
|
||||
|
||||
/**
|
||||
* If items have to be noted.
|
||||
* @return If items have to be noted {@code true}.
|
||||
*/
|
||||
public boolean isNoteItems() {
|
||||
return player.varpManager.getVarbit(Vars.VARBIT_IFACE_BANK_NOTE_MODE) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if items have to be noted.
|
||||
* @param noteItems If items have to be noted {@code true}.
|
||||
*/
|
||||
public void setNoteItems(boolean noteItems) {
|
||||
player.varpManager.flagSave(Vars.VARBIT_IFACE_BANK_NOTE_MODE, true);
|
||||
player.varpManager.setVarbit(Vars.VARBIT_IFACE_BANK_NOTE_MODE, noteItems ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tabStartSlot value.
|
||||
* @return The tabStartSlot.
|
||||
*/
|
||||
public int[] getTabStartSlot() {
|
||||
return tabStartSlot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tabIndex value.
|
||||
* @return The tabIndex.
|
||||
*/
|
||||
public int getTabIndex() {
|
||||
return tabIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tabIndex value.
|
||||
* @param tabIndex The tabIndex to set.
|
||||
*/
|
||||
public void setTabIndex(int tabIndex) {
|
||||
this.tabIndex = tabIndex;
|
||||
|
||||
/*
|
||||
* Kludge to update the interface
|
||||
* after dumping all to prevent
|
||||
* "invisible" items in slots.
|
||||
*/
|
||||
update(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the insert items value.
|
||||
* @param insertItems The insert items value.
|
||||
*/
|
||||
public void setInsertItems(boolean insertItems) {
|
||||
player.varpManager.setVarbit(Vars.VARBIT_IFACE_BANK_INSERT_MODE, insertItems ? 1 : 0);
|
||||
player.varpManager.flagSave(Vars.VARBIT_IFACE_BANK_INSERT_MODE, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the insert items value.
|
||||
* @return {@code True} if inserting items mode is enabled.
|
||||
*/
|
||||
public boolean isInsertItems() {
|
||||
return player.varpManager.getVarbit(Vars.VARBIT_IFACE_BANK_INSERT_MODE) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the bank is opened.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to the bank container.
|
||||
* @author Emperor
|
||||
*/
|
||||
private static class BankListener implements ContainerListener {
|
||||
|
||||
/**
|
||||
* The player reference.
|
||||
*/
|
||||
private Player player;
|
||||
|
||||
/**
|
||||
* Construct a new {@code BankListener} {@code Object}.
|
||||
* @param player The player reference.
|
||||
*/
|
||||
public BankListener(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Container c, ContainerEvent event) {
|
||||
if (c instanceof BankContainer) {
|
||||
PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 762, 64000, 95, event.getItems(), false, event.getSlots()));
|
||||
} else {
|
||||
PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 763, 64000, 93, event.getItems(), false, event.getSlots()));
|
||||
}
|
||||
player.getBank().setTabConfigurations();
|
||||
player.getBank().sendBankSpace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(Container c) {
|
||||
if (c instanceof BankContainer) {
|
||||
PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 762, 64000, 95, c.toArray(), c.capacity(), false));
|
||||
} else {
|
||||
PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 763, 64000, 93, c.toArray(), 28, false));
|
||||
}
|
||||
player.getBank().setTabConfigurations();
|
||||
player.getBank().sendBankSpace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
package core.game.container.impl;
|
||||
|
||||
import content.global.skill.skillcapeperks.SkillcapePerks;
|
||||
import core.game.container.Container;
|
||||
import core.game.container.ContainerEvent;
|
||||
import core.game.container.ContainerListener;
|
||||
import core.game.node.entity.combat.equipment.WeaponInterface;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.entity.skill.Skills;
|
||||
import core.game.node.item.Item;
|
||||
import core.game.world.update.flag.player.AppearanceFlag;
|
||||
import core.net.packet.PacketRepository;
|
||||
import core.net.packet.context.ContainerContext;
|
||||
import core.net.packet.out.ContainerPacket;
|
||||
import core.net.packet.out.WeightUpdate;
|
||||
import core.plugin.Plugin;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import core.game.interaction.InteractionListeners;
|
||||
import core.game.system.config.ItemConfigParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Represents the equipment container.
|
||||
* @author Emperor
|
||||
*/
|
||||
public final class EquipmentContainer extends Container {
|
||||
/**
|
||||
* The equipment slots.
|
||||
*/
|
||||
public static final int SLOT_HAT = 0, SLOT_CAPE = 1, SLOT_AMULET = 2, SLOT_WEAPON = 3, SLOT_CHEST = 4, SLOT_SHIELD = 5, SLOT_LEGS = 7, SLOT_HANDS = 9, SLOT_FEET = 10, SLOT_RING = 12, SLOT_ARROWS = 13;
|
||||
|
||||
/**
|
||||
* The bonus names.
|
||||
*/
|
||||
private static final String[] BONUS_NAMES = { "Stab: ", "Slash: ", "Crush: ", "Magic: ", "Ranged: ", "Stab: ", "Slash: ", "Crush: ", "Magic: ", "Ranged: ", "Summoning: ", "Strength: ", "Prayer: " };
|
||||
|
||||
/**
|
||||
* The player.
|
||||
*/
|
||||
private final Player player;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code EquipmentContainer} {@code Object}.
|
||||
* @param player The player.
|
||||
*/
|
||||
public EquipmentContainer(Player player) {
|
||||
super(14);
|
||||
this.player = player;
|
||||
register(new EquipmentListener(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Item item, boolean fire) {
|
||||
return add(item, fire, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item to the equipment container.
|
||||
* @param item The item to add.
|
||||
* @param fire If we should refresh.
|
||||
* @param fromInventory If the item is being equipped from the inventory.
|
||||
* @return {@code True} if succesful, {@code false} if not.
|
||||
*/
|
||||
public boolean add(Item item, boolean fire, boolean fromInventory) {
|
||||
return add(item, player.getInventory().getSlot(item), fire, fromInventory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item to the equipment container.
|
||||
* @param newItem The item to add.
|
||||
* @param inventorySlot The inventory slot of the item to add.
|
||||
* @param fire If we should refresh.
|
||||
* @param fromInventory If the item is being equipped from the inventory.
|
||||
* @return {@code True} if succesful, {@code false} if not.
|
||||
*/
|
||||
public boolean add(Item newItem, int inventorySlot, boolean fire, boolean fromInventory) {
|
||||
int equipmentSlot = newItem.getDefinition().getConfiguration(ItemConfigParser.EQUIP_SLOT, -1);
|
||||
if (!isEquippable(newItem, equipmentSlot)) return false;
|
||||
|
||||
ArrayList<Item> itemsToRemove = new ArrayList<>();
|
||||
|
||||
Item currentItem = super.get(equipmentSlot);
|
||||
if(currentItem != null) itemsToRemove.add(currentItem);
|
||||
|
||||
Item secondaryEquip = getSecondaryEquipIfApplicable(newItem, equipmentSlot);
|
||||
if(secondaryEquip != null) itemsToRemove.add(secondaryEquip);
|
||||
|
||||
if(fromInventory && !player.getInventory().remove(newItem, inventorySlot, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!itemsToRemove.isEmpty()) {
|
||||
ArrayList<Item> invalidatedEntries = new ArrayList<>();
|
||||
for(Item current : itemsToRemove) {
|
||||
if(current.getId() == newItem.getId() && current.getDefinition().isStackable()) {
|
||||
addStackableItemToExistingStack(newItem, fromInventory, equipmentSlot, current);
|
||||
invalidatedEntries.add(current);
|
||||
}
|
||||
}
|
||||
itemsToRemove.removeAll(invalidatedEntries);
|
||||
|
||||
if(itemsToRemove.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean successfullyRemovedAll = tryUnequipCurrent(itemsToRemove, newItem, inventorySlot);
|
||||
|
||||
if(!successfullyRemovedAll) {
|
||||
if (fromInventory) player.getInventory().add(newItem); //add the item back in case we weren't able to remove the currently equipped item(s)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
super.replace(newItem, equipmentSlot, fire);
|
||||
setWeaponInterfaceWeaponName(newItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isEquippable(Item newItem, int slot) {
|
||||
if (slot < 0) {
|
||||
return false;
|
||||
}
|
||||
return newItem.getDefinition().hasRequirement(player, true, true);
|
||||
}
|
||||
|
||||
private void setWeaponInterfaceWeaponName(Item newItem) {
|
||||
if (newItem.getSlot() == SLOT_WEAPON) {
|
||||
player.getPacketDispatch().sendString(newItem.getName(), 92, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean tryUnequipCurrent(ArrayList<Item> current, Item newItem, int preferredSlot) {
|
||||
if(current.isEmpty()) return true;
|
||||
int freeSlots = player.getInventory().freeSlots();
|
||||
int neededSlots = getNeededSlotsToUnequip(current);
|
||||
|
||||
boolean hasSpaceForUnequippedItems = freeSlots >= neededSlots;
|
||||
|
||||
if(!hasSpaceForUnequippedItems) {
|
||||
player.getPacketDispatch().sendMessage("Not enough space in your inventory!");
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean listenersSayWeCanUnequip = runUnequipHooks(current, newItem);
|
||||
|
||||
boolean allRemoved = true;
|
||||
for(Item item : current) {
|
||||
if(!remove(item)) {
|
||||
allRemoved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
boolean allAdded = allRemoved;
|
||||
if(allRemoved) {
|
||||
for (Item item : current) {
|
||||
if (!player.getInventory().add(item, true, preferredSlot)) {
|
||||
allAdded = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (listenersSayWeCanUnequip && allRemoved && allAdded) return true;
|
||||
else {
|
||||
//put things back if we couldn't remove everything
|
||||
for(Item item : current) {
|
||||
if(!containsItem(item)) {
|
||||
add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int getNeededSlotsToUnequip(ArrayList<Item> current) {
|
||||
int neededSlots = 0;
|
||||
|
||||
for(Item item : current) {
|
||||
if(!item.getDefinition().isStackable()) {
|
||||
neededSlots++;
|
||||
} else {
|
||||
if(player.getInventory().getAmount(item.getId()) == 0) {
|
||||
neededSlots++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return neededSlots;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Item getSecondaryEquipIfApplicable(Item newItem, int equipmentSlot) {
|
||||
Item secondaryEquipItem = null;
|
||||
if (newItem.getDefinition().getConfiguration(ItemConfigParser.TWO_HANDED, false)) {
|
||||
secondaryEquipItem = get(SLOT_SHIELD);
|
||||
} else if (equipmentSlot == SLOT_SHIELD) {
|
||||
Item inSlot = player.getEquipment().get(SLOT_WEAPON);
|
||||
if(inSlot != null && inSlot.getDefinition().getConfiguration(ItemConfigParser.TWO_HANDED, false))
|
||||
secondaryEquipItem = get(SLOT_WEAPON);
|
||||
}
|
||||
return secondaryEquipItem;
|
||||
}
|
||||
|
||||
private boolean runUnequipHooks(ArrayList<Item> currentItems, Item newItem) {
|
||||
boolean canContinue = true;
|
||||
|
||||
for(Item currentItem : currentItems) {
|
||||
Plugin<Object> plugin = currentItem.getDefinition().getConfiguration("equipment", null);
|
||||
if (plugin != null) {
|
||||
Object object = plugin.fireEvent("unequip", player, currentItem, newItem);
|
||||
if (object != null && !((Boolean) object)) {
|
||||
canContinue = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
canContinue = InteractionListeners.run(currentItem.getId(), player, currentItem, false);
|
||||
|
||||
if(!canContinue) break;
|
||||
}
|
||||
|
||||
return canContinue;
|
||||
}
|
||||
|
||||
private void addStackableItemToExistingStack(Item item, boolean fromInventory, int slot, Item current) {
|
||||
int amount = getMaximumAdd(item);
|
||||
if (item.getAmount() > amount) {
|
||||
amount += current.getAmount();
|
||||
} else {
|
||||
amount = current.getAmount() + item.getAmount();
|
||||
}
|
||||
Item transferItem = new Item(current.getId(), amount);
|
||||
if (fromInventory) {
|
||||
player.getInventory().remove(transferItem);
|
||||
}
|
||||
replace(transferItem, slot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to the equipment container.
|
||||
* @author Emperor
|
||||
*/
|
||||
private static class EquipmentListener implements ContainerListener {
|
||||
|
||||
/**
|
||||
* The player.
|
||||
*/
|
||||
private final Player player;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code EquipmentContainer} {@code Object}.
|
||||
* @param player The player.
|
||||
*/
|
||||
public EquipmentListener(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Container c, ContainerEvent event) {
|
||||
int[] slots = event.getSlots();
|
||||
PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 387, 28, 94, event.getItems(), false, slots));
|
||||
update(c);
|
||||
boolean updateDefenceAnimation = false;
|
||||
for (int slot : slots) {
|
||||
if (slot == EquipmentContainer.SLOT_WEAPON) {
|
||||
player.getProperties().setAttackSpeed(c.getNew(slot).getDefinition().getConfiguration(ItemConfigParser.ATTACK_SPEED, 4));
|
||||
WeaponInterface inter = player.getExtension(WeaponInterface.class);
|
||||
if (inter == null) {
|
||||
break;
|
||||
}
|
||||
inter.updateInterface();
|
||||
updateDefenceAnimation = true;
|
||||
} else if (slot == EquipmentContainer.SLOT_SHIELD) {
|
||||
updateDefenceAnimation = true;
|
||||
}
|
||||
}
|
||||
if (updateDefenceAnimation) {
|
||||
player.getProperties().updateDefenceAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(Container c) {
|
||||
player.getProperties().setAttackSpeed(c.getNew(3).getDefinition().getConfiguration(ItemConfigParser.ATTACK_SPEED, 4));
|
||||
WeaponInterface inter = player.getExtension(WeaponInterface.class);
|
||||
if (inter != null) {
|
||||
inter.updateInterface();
|
||||
}
|
||||
PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 387, 28, 94, c.toArray(), 14, false));
|
||||
update(c);
|
||||
player.getProperties().updateDefenceAnimation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the bonuses, weight, animations, ...
|
||||
* @param c The container.
|
||||
*/
|
||||
public void update(Container c) {
|
||||
if (c.getNew(SLOT_SHIELD).getId() != 11283 && player.getAttribute("dfs_spec", false)) {
|
||||
player.removeAttribute("dfs_spec");
|
||||
player.getProperties().getCombatPulse().setHandler(null);
|
||||
if (!player.getSettings().isSpecialToggled()) {
|
||||
player.getConfigManager().set(301, 0);
|
||||
}
|
||||
}
|
||||
player.getAppearance().setAnimations();
|
||||
player.getUpdateMasks().register(new AppearanceFlag(player));
|
||||
player.getSettings().updateWeight();
|
||||
updateBonuses(player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the bonuses.
|
||||
* @param player The player.
|
||||
*/
|
||||
public static void updateBonuses(Player player) {
|
||||
int[] bonuses = new int[15];
|
||||
for (Item item : player.getEquipment().toArray()) {
|
||||
if (item != null) {
|
||||
int[] bonus = item.getDefinition().getConfiguration(ItemConfigParser.BONUS, new int[15]);
|
||||
for (int i = 0; i < bonus.length; i++) {
|
||||
if (i == 14 && bonuses[i] != 0) {
|
||||
continue;
|
||||
}
|
||||
bonuses[i] += bonus[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
Item weapon = player.getEquipment().get(SLOT_WEAPON);
|
||||
if(weapon != null && weapon.getDefinition().getRequirement(Skills.STRENGTH) > 0 && SkillcapePerks.isActive(SkillcapePerks.FINE_ATTUNEMENT,player)){
|
||||
int[] bonus = weapon.getDefinition().getConfiguration(ItemConfigParser.BONUS, new int[15]);
|
||||
bonuses[11] += Math.ceil(bonus[11] * 0.20);
|
||||
}
|
||||
Item shield = player.getEquipment().get(SLOT_SHIELD);
|
||||
if(shield != null && SkillcapePerks.isActive(SkillcapePerks.GRAND_BULLWARK,player)){
|
||||
bonuses[5] += Math.ceil(bonuses[5] * 0.20);
|
||||
bonuses[6] += Math.ceil(bonuses[6] * 0.20);
|
||||
bonuses[7] += Math.ceil(bonuses[7] * 0.20);
|
||||
bonuses[9] += Math.ceil(bonuses[9] * 0.20);
|
||||
bonuses[10] += Math.ceil(bonuses[10] * 0.20);
|
||||
}
|
||||
if (shield != null && shield.getId() == 11283) {
|
||||
int increase = shield.getCharge() / 20;
|
||||
bonuses[5] += increase;
|
||||
bonuses[6] += increase;
|
||||
bonuses[7] += increase;
|
||||
bonuses[9] += increase;
|
||||
bonuses[10] += increase;
|
||||
}
|
||||
player.getProperties().setBonuses(bonuses);
|
||||
update(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the equipment stats interface.
|
||||
* @param player The player to update for.
|
||||
*/
|
||||
public static void update(Player player) {
|
||||
if (!player.getInterfaceManager().hasMainComponent(667)) {
|
||||
return;
|
||||
}
|
||||
PacketRepository.send(WeightUpdate.class, player.getPacketDispatch().getContext());
|
||||
int index = 0;
|
||||
int[] bonuses = player.getProperties().getBonuses();
|
||||
for (int i = 36; i < 50; i++) {
|
||||
if (i == 47) {
|
||||
continue;
|
||||
}
|
||||
int bonus = bonuses[index];
|
||||
String bonusValue = bonus > -1 ? ("+" + bonus) : Integer.toString(bonus);
|
||||
player.getPacketDispatch().sendString(BONUS_NAMES[index++] + bonusValue, 667, i);
|
||||
}
|
||||
player.getPacketDispatch().sendString("Attack bonus", 667, 34);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package core.game.container.impl;
|
||||
|
||||
import core.game.container.Container;
|
||||
import core.game.container.ContainerEvent;
|
||||
import core.game.container.ContainerListener;
|
||||
import content.global.skill.summoning.SummoningPouch;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
import core.net.packet.PacketRepository;
|
||||
import core.net.packet.context.ContainerContext;
|
||||
import core.net.packet.out.ContainerPacket;
|
||||
|
||||
/**
|
||||
* Handles the inventory container listening.
|
||||
* @author Emperor
|
||||
*/
|
||||
public final class InventoryListener implements ContainerListener {
|
||||
|
||||
/**
|
||||
* The player.
|
||||
*/
|
||||
private final Player player;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code InventoryListener} {@code Object}.
|
||||
* @param player The player.
|
||||
*/
|
||||
public InventoryListener(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the required settings etc for the player when the container
|
||||
* updates.
|
||||
* @param c The container.
|
||||
*/
|
||||
public void update(Container c) {
|
||||
player.getSettings().updateWeight();
|
||||
boolean hadPouch = player.getFamiliarManager().isHasPouch();
|
||||
boolean pouch = false;
|
||||
for (Item item : c.toArray()) {
|
||||
if (item != null && SummoningPouch.get(item.getId()) != null) {
|
||||
pouch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
player.getFamiliarManager().setHasPouch(pouch);
|
||||
if (hadPouch != pouch && player.getSkullManager().isWilderness()) {
|
||||
player.getAppearance().sync();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(Container c) {
|
||||
PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 149, 0, 93, c, false));
|
||||
update(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Container c, ContainerEvent event) {
|
||||
PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 149, 0, 93, event.getItems(), false, event.getSlots()));
|
||||
update(c);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package core.game.dialogue;
|
||||
|
||||
import content.global.skill.slayer.SlayerManager;
|
||||
import core.cache.def.impl.ItemDefinition;
|
||||
import core.game.interaction.OptionHandler;
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.plugin.Initializable;
|
||||
import core.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Represents the plugin used to handle the enchanted gem related to slayer.
|
||||
* @author 'Vexia
|
||||
* @version 1.0
|
||||
*/
|
||||
@Initializable
|
||||
public final class ActivateEnchantedGem extends OptionHandler {
|
||||
|
||||
/**
|
||||
* Constructs a new {@code ActivateEnchantedGem} {@Code Object}.
|
||||
*/
|
||||
public ActivateEnchantedGem() {
|
||||
/**
|
||||
* empty.
|
||||
*/
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(Player player, Node node, String option) {
|
||||
if (!SlayerManager.getInstance(player).hasStarted()) {
|
||||
player.getPacketDispatch().sendMessage("You try to activate the gem...");
|
||||
return true;
|
||||
}
|
||||
player.getDialogueInterpreter().open(77777);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWalk() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin<Object> newInstance(Object arg) throws Throwable {
|
||||
ItemDefinition.forId(4155).getHandlers().put("option:activate", this);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package core.game.dialogue;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
|
||||
/**
|
||||
* A dialogue reward.
|
||||
* @author Vexia
|
||||
*/
|
||||
public interface DialogueAction {
|
||||
|
||||
/**
|
||||
* Handles a dialogue click.
|
||||
* @param player the player.
|
||||
* @param buttonId the buttonId.
|
||||
*/
|
||||
public void handle(Player player, int buttonId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package core.game.dialogue
|
||||
import core.game.node.entity.player.Player
|
||||
import core.tools.END_DIALOGUE
|
||||
|
||||
abstract class DialogueBuilderFile : core.game.dialogue.DialogueFile() {
|
||||
var data: ArrayList<core.game.dialogue.DialogueClause> = ArrayList()
|
||||
//var stages: ArrayList<Int> = ArrayList()
|
||||
abstract fun create(b: core.game.dialogue.DialogueBuilder)
|
||||
init {
|
||||
create(core.game.dialogue.DialogueBuilder(this))
|
||||
}
|
||||
override fun handle(componentID: Int, buttonID: Int) {
|
||||
for((i, clause) in data.iterator().withIndex()) {
|
||||
if(clause.predicate(player!!)) {
|
||||
stage = clause.handle(this, componentID, buttonID, stage)
|
||||
if(stage == END_DIALOGUE) {
|
||||
end()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface DialogueNode {
|
||||
fun handle(df: core.game.dialogue.DialogueFile, componentID: Int, buttonID: Int, stage: Int): Int
|
||||
}
|
||||
|
||||
class NpcLNode(val value: String): core.game.dialogue.DialogueNode {
|
||||
override fun handle(df: core.game.dialogue.DialogueFile, componentID: Int, buttonID: Int, stage: Int): Int {
|
||||
df.npcl(value)
|
||||
return stage + 1
|
||||
}
|
||||
}
|
||||
class NpcNode(val values: Array<String>): core.game.dialogue.DialogueNode {
|
||||
override fun handle(df: core.game.dialogue.DialogueFile, componentID: Int, buttonID: Int, stage: Int): Int {
|
||||
df.npc(*values)
|
||||
return stage + 1
|
||||
}
|
||||
}
|
||||
class PlayerLNode(val value: String): core.game.dialogue.DialogueNode {
|
||||
override fun handle(df: core.game.dialogue.DialogueFile, componentID: Int, buttonID: Int, stage: Int): Int {
|
||||
df.playerl(value)
|
||||
return stage + 1
|
||||
}
|
||||
}
|
||||
class ClosureNode(val f: (Player) -> Int): core.game.dialogue.DialogueNode {
|
||||
override fun handle(df: core.game.dialogue.DialogueFile, componentID: Int, buttonID: Int, stage: Int): Int {
|
||||
return f(df.player!!)
|
||||
}
|
||||
}
|
||||
|
||||
class OptionEntry(val text: String, val nextStage: Int, val predicate: (Player) -> Boolean = { _ -> true }) {}
|
||||
|
||||
class OptionsNode(var options: ArrayList<core.game.dialogue.OptionEntry>): core.game.dialogue.DialogueNode {
|
||||
fun optionNames(player: Player): Array<String?> {
|
||||
return options.asSequence().filter({ it.predicate(player) }).map({ it.text }).toList().toTypedArray()
|
||||
}
|
||||
override fun handle(df: core.game.dialogue.DialogueFile, componentID: Int, buttonID: Int, stage: Int): Int {
|
||||
val tmp: Array<String?> = optionNames(df.player!!)
|
||||
if(tmp.size > 1) {
|
||||
df.options(*tmp)
|
||||
return stage + 1
|
||||
} else if(tmp.size == 1) {
|
||||
val tmp: List<core.game.dialogue.OptionEntry> = options.asSequence().filter({ it.predicate(df.player!!) }).toList()
|
||||
df.stage = tmp[0].nextStage
|
||||
df.handle(componentID, 0)
|
||||
return df.stage
|
||||
} else {
|
||||
return END_DIALOGUE
|
||||
}
|
||||
}
|
||||
}
|
||||
class OptionsDispatchNode(var options: ArrayList<core.game.dialogue.OptionEntry>): core.game.dialogue.DialogueNode {
|
||||
override fun handle(df: core.game.dialogue.DialogueFile, componentID: Int, buttonID: Int, stage: Int): Int {
|
||||
val tmp: List<core.game.dialogue.OptionEntry> = options.asSequence().filter({ it.predicate(df.player!!) }).toList()
|
||||
df.stage = tmp[buttonID-1].nextStage
|
||||
df.handle(componentID, 0)
|
||||
return df.stage
|
||||
}
|
||||
}
|
||||
|
||||
class DialogueClause(val predicate: (player: Player) -> Boolean, val nodes: ArrayList<core.game.dialogue.DialogueNode>) {
|
||||
fun handle(df: core.game.dialogue.DialogueFile, componentID: Int, buttonID: Int, stage: Int): Int {
|
||||
if(stage < nodes.size) {
|
||||
return nodes[stage].handle(df, componentID, buttonID, stage)
|
||||
} else {
|
||||
return END_DIALOGUE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DialogueOptionsBuilder(var target: core.game.dialogue.DialogueBuilderFile, val clauseIndex: Int, var options: ArrayList<core.game.dialogue.OptionEntry>) {
|
||||
fun option(value: String): core.game.dialogue.DialogueBuilder {
|
||||
options.add(core.game.dialogue.OptionEntry(value, target.data[clauseIndex].nodes.size))
|
||||
return core.game.dialogue.DialogueBuilder(target, clauseIndex)
|
||||
}
|
||||
|
||||
fun optionIf(value: String, predicate: (Player) -> Boolean): core.game.dialogue.DialogueBuilder {
|
||||
options.add(core.game.dialogue.OptionEntry(value, target.data[clauseIndex].nodes.size, predicate))
|
||||
return core.game.dialogue.DialogueBuilder(target, clauseIndex)
|
||||
}
|
||||
}
|
||||
|
||||
class DialogueBuilder(var target: core.game.dialogue.DialogueBuilderFile, var clauseIndex: Int = -1) {
|
||||
fun onPredicate(predicate: (player: Player) -> Boolean): core.game.dialogue.DialogueBuilder {
|
||||
target.data.add(core.game.dialogue.DialogueClause(predicate, ArrayList()))
|
||||
clauseIndex = target.data.size - 1
|
||||
return this
|
||||
}
|
||||
fun onQuestStages(name: String, vararg stages: Int): core.game.dialogue.DialogueBuilder {
|
||||
return onPredicate() { player ->
|
||||
val questStage = player.questRepository.getStage(name)
|
||||
return@onPredicate stages.contains(questStage)
|
||||
}
|
||||
}
|
||||
fun playerl(value: String): core.game.dialogue.DialogueBuilder {
|
||||
target.data[clauseIndex].nodes.add(core.game.dialogue.PlayerLNode(value))
|
||||
return this
|
||||
}
|
||||
fun npcl(value: String): core.game.dialogue.DialogueBuilder {
|
||||
target.data[clauseIndex].nodes.add(core.game.dialogue.NpcLNode(value))
|
||||
return this
|
||||
}
|
||||
fun npc(vararg values: String): core.game.dialogue.DialogueBuilder {
|
||||
target.data[clauseIndex].nodes.add(core.game.dialogue.NpcNode(values as Array<String>))
|
||||
return this
|
||||
}
|
||||
fun endWith(f: (Player) -> Unit) {
|
||||
target.data[clauseIndex].nodes.add(core.game.dialogue.ClosureNode({ player ->
|
||||
f(player)
|
||||
return@ClosureNode END_DIALOGUE
|
||||
}))
|
||||
}
|
||||
fun end() {
|
||||
target.data[clauseIndex].nodes.add(core.game.dialogue.ClosureNode({ _ ->
|
||||
return@ClosureNode END_DIALOGUE
|
||||
}))
|
||||
}
|
||||
fun options(): core.game.dialogue.DialogueOptionsBuilder {
|
||||
var options: ArrayList<core.game.dialogue.OptionEntry> = ArrayList()
|
||||
val node = core.game.dialogue.OptionsNode(options)
|
||||
val dispatchNode = core.game.dialogue.OptionsDispatchNode(options)
|
||||
target.data[clauseIndex].nodes.add(node)
|
||||
target.data[clauseIndex].nodes.add(dispatchNode)
|
||||
return core.game.dialogue.DialogueOptionsBuilder(target, clauseIndex, options)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package core.game.dialogue
|
||||
|
||||
import core.api.splitLines
|
||||
import core.game.component.Component
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.node.entity.npc.NPC
|
||||
import core.game.node.entity.player.Player
|
||||
import core.tools.START_DIALOGUE
|
||||
|
||||
abstract class DialogueFile {
|
||||
var player: Player? = null
|
||||
var npc: NPC? = null
|
||||
var interpreter: core.game.dialogue.DialogueInterpreter? = null
|
||||
open var stage = START_DIALOGUE
|
||||
var dialoguePlugin: core.game.dialogue.DialoguePlugin? = null
|
||||
|
||||
abstract fun handle(componentID: Int, buttonID: Int)
|
||||
|
||||
fun load(player: Player, npc: NPC?, interpreter: core.game.dialogue.DialogueInterpreter): DialogueFile {
|
||||
this.player = player
|
||||
this.npc = npc
|
||||
this.interpreter = interpreter
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
open fun npc(vararg messages: String?): Component? {
|
||||
if (npc != null) {
|
||||
return interpreter!!.sendDialogues(
|
||||
npc,
|
||||
if (npc!!.id > 8591) core.game.dialogue.FacialExpression.OLD_NORMAL else core.game.dialogue.FacialExpression.FRIENDLY,
|
||||
*messages
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
open fun npc(id: Int, vararg messages: String?): Component? {
|
||||
return interpreter!!.sendDialogues(id, core.game.dialogue.FacialExpression.FRIENDLY, *messages)
|
||||
}
|
||||
|
||||
open fun npc(expression: core.game.dialogue.FacialExpression?, vararg messages: String?): Component? {
|
||||
return if (npc == null) {
|
||||
interpreter!!.sendDialogues(0, expression, *messages)
|
||||
} else interpreter!!.sendDialogues(npc, expression, *messages)
|
||||
}
|
||||
|
||||
open fun player(vararg messages: String?): Component? {
|
||||
return interpreter!!.sendDialogues(player, null, *messages)
|
||||
}
|
||||
|
||||
open fun player(expression: core.game.dialogue.FacialExpression?, vararg messages: String?): Component? {
|
||||
return interpreter!!.sendDialogues(player, expression, *messages)
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the automatic linesplitting feature in DialUtils to produce npc dialogues
|
||||
* @param expr the FacialExpression to use, located in the FacialExpression enum.
|
||||
* @param msg the message for the NPC to say
|
||||
*/
|
||||
open fun npcl(expr: core.game.dialogue.FacialExpression?, msg: String?): Component? {
|
||||
return npc(expr, *splitLines(msg!!))
|
||||
}
|
||||
|
||||
open fun npcl(msg: String?): Component? {
|
||||
return npc(*splitLines(msg!!))
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the automatic linesplitting feature in DialUtils to produce player dialogues
|
||||
* @param expr the FacialExpression to use, located in the FacialExpression enum.
|
||||
* @param msg the message for the player to say
|
||||
*/
|
||||
open fun playerl(expr: core.game.dialogue.FacialExpression?, msg: String?): Component? {
|
||||
return player(expr, *splitLines(msg!!))
|
||||
}
|
||||
|
||||
open fun playerl(msg: String?): Component? {
|
||||
return player(*splitLines(msg!!))
|
||||
}
|
||||
|
||||
open fun end(){
|
||||
if(interpreter != null) interpreter!!.close()
|
||||
}
|
||||
|
||||
open fun sendNormalDialogue(entity: Entity?, expression: core.game.dialogue.FacialExpression?, vararg messages: String?) {
|
||||
interpreter!!.sendDialogues(entity, expression, *messages)
|
||||
}
|
||||
|
||||
open fun options(vararg options: String?) {
|
||||
interpreter!!.sendOptions("Select an Option", *options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Use in place of setting the stage to END_DIALOGUE when you want to return to the default dialogue plugin at START_DIALOGUE
|
||||
*/
|
||||
fun endFile(){
|
||||
interpreter!!.dialogue.file = null
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return to the default dialogue plugin at the given stage.
|
||||
* Should be used in place of setting a new stage. I.E. instead of "stage = END_DIALOGUE" use "returnAtStage(whatever stage)"
|
||||
* @param stage The stage to return to.
|
||||
*/
|
||||
fun returnAtStage(toStage: Int){
|
||||
dialoguePlugin!!.file = null
|
||||
dialoguePlugin!!.stage = toStage
|
||||
}
|
||||
|
||||
/**
|
||||
* Use when you've entered a DialogueFile but current state does not match any possible conditionals.
|
||||
* Sort-of a fail-safe in a sense.
|
||||
*/
|
||||
fun abandonFile(){
|
||||
interpreter!!.dialogue.file = null
|
||||
player("Huh. Nevermind.")
|
||||
}
|
||||
|
||||
open fun getCurrentStage(): Int{
|
||||
return stage
|
||||
}
|
||||
|
||||
fun Int.substage(stage: Int): Int{
|
||||
return this + stage
|
||||
}
|
||||
|
||||
fun dialogue(vararg messages: String){
|
||||
player?.dialogueInterpreter?.sendDialogue(*messages)
|
||||
}
|
||||
|
||||
fun showTopics(vararg topics: Topic<*>) {
|
||||
val validTopics = ArrayList<String>()
|
||||
topics.filter { if(it is IfTopic) it.showCondition else true }.forEach {
|
||||
topic -> interpreter!!.activeTopics.add(topic)
|
||||
validTopics.add(topic.text)
|
||||
}
|
||||
if (validTopics.size == 1) {
|
||||
val topic = topics[0]
|
||||
if(topic.toStage is DialogueFile) {
|
||||
val topicFile = topic.toStage as DialogueFile
|
||||
interpreter!!.dialogue.loadFile(topicFile)
|
||||
} else if(topic.toStage is Int) {
|
||||
stage = topic.toStage as Int
|
||||
}
|
||||
player(topic.text)
|
||||
interpreter!!.activeTopics.clear()
|
||||
}
|
||||
else options(*validTopics.toTypedArray())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,575 @@
|
||||
package core.game.dialogue;
|
||||
|
||||
import core.game.event.DialogueCloseEvent;
|
||||
import core.game.event.DialogueOpenEvent;
|
||||
import core.game.event.DialogueOptionSelectionEvent;
|
||||
import core.cache.def.impl.ItemDefinition;
|
||||
import core.cache.def.impl.NPCDefinition;
|
||||
import core.game.component.Component;
|
||||
import core.game.node.entity.Entity;
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
import core.net.packet.PacketRepository;
|
||||
import core.net.packet.context.ContainerContext;
|
||||
import core.net.packet.out.ContainerPacket;
|
||||
import core.plugin.PluginManifest;
|
||||
import core.plugin.PluginType;
|
||||
import core.game.system.config.ItemConfigParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static core.tools.DialogueConstKt.END_DIALOGUE;
|
||||
|
||||
|
||||
/**
|
||||
* Handles the dialogues.
|
||||
* @author Emperor
|
||||
*/
|
||||
@PluginManifest(type = PluginType.DIALOGUE)
|
||||
public final class DialogueInterpreter {
|
||||
public ArrayList<Topic<?>> activeTopics = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* The dialogue plugins.
|
||||
*/
|
||||
private static final Map<Integer, DialoguePlugin> PLUGINS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* a List of dialogue actions.
|
||||
*/
|
||||
private final List<DialogueAction> actions = new ArrayList<>(10);
|
||||
|
||||
/**
|
||||
* The currently opened dialogue.
|
||||
*/
|
||||
private DialoguePlugin dialogue;
|
||||
|
||||
/**
|
||||
* The current dialogue key.
|
||||
*/
|
||||
private int key;
|
||||
|
||||
/**
|
||||
* The player.
|
||||
*/
|
||||
private final Player player;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code DialogueInterpreter} {@code Object}.
|
||||
* @param player The player.
|
||||
*/
|
||||
public DialogueInterpreter(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param dialogue the dialogue to set.
|
||||
*/
|
||||
public void setDialogue(DialoguePlugin dialogue) {
|
||||
this.dialogue = dialogue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the dialogue for the given dialogue type.
|
||||
* @param dialogueType The dialogue type.
|
||||
* @param args the args.
|
||||
* @return {@code True} if successful.
|
||||
*/
|
||||
public boolean open(String dialogueType, Object... args) {
|
||||
return open(getDialogueKey(dialogueType), args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the dialogue for the given NPC id.
|
||||
* @param dialogueKey The dialogue key (usually NPC id).
|
||||
* @param args The arguments.
|
||||
* @return {@code True} if successful.
|
||||
*/
|
||||
public boolean open(int dialogueKey, Object... args) {
|
||||
key = dialogueKey;
|
||||
if (args.length > 0 && args[0] instanceof NPC) {
|
||||
NPC npc = (NPC) args[0];
|
||||
npc.setDialoguePlayer(player);
|
||||
try { //this prevents the dialogue interpreter from dying when you try opening an NPC's dialogue without passing the NPC itself as an argument
|
||||
npc.getWalkingQueue().reset();
|
||||
npc.getPulseManager().clear();
|
||||
} catch(Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else if (args.length < 1) {
|
||||
args = new Object[] { dialogueKey };
|
||||
}
|
||||
DialoguePlugin plugin = PLUGINS.get(dialogueKey);
|
||||
if (plugin == null) {
|
||||
return false;
|
||||
}
|
||||
if (player.isDebug()) {
|
||||
player.sendMessage("Dialogue opening - " + plugin.getClass().getSimpleName() + ", key=" + dialogueKey + "");
|
||||
}
|
||||
this.dialogue = plugin.newInstance(player);
|
||||
if (dialogue == null || !dialogue.open(args)) {
|
||||
dialogue = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dialogue != null) {
|
||||
player.dispatch(new DialogueOpenEvent(dialogue));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void open(DialogueFile file, Object... args){
|
||||
this.dialogue = new EmptyPlugin(player,file);
|
||||
this.dialogue.open(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an dialogue input.
|
||||
* @param componentId The id of the chatbox component.
|
||||
* @param buttonId The button id.
|
||||
*/
|
||||
public void handle(int componentId, int buttonId) {
|
||||
player.setAttribute("chatbox-buttonid",buttonId);
|
||||
if((player.getDialogueInterpreter().getDialogue().file != null && player.getDialogueInterpreter().getDialogue().file.getCurrentStage() == END_DIALOGUE) || player.getDialogueInterpreter().getDialogue().stage == END_DIALOGUE){
|
||||
player.getInterfaceManager().closeChatbox();
|
||||
player.getInterfaceManager().openChatbox(137);
|
||||
player.getDialogueInterpreter().dialogue.finished = true;
|
||||
close();
|
||||
return;
|
||||
}
|
||||
DialogueFile file = dialogue.file;
|
||||
if (!activeTopics.isEmpty()) {
|
||||
Topic<?> topic = activeTopics.get(buttonId - 2);
|
||||
|
||||
if (!topic.getSkipPlayer())
|
||||
sendDialogues(player, topic.getExpr(), topic.getText());
|
||||
|
||||
if(topic.getToStage() instanceof DialogueFile) {
|
||||
DialogueFile topicFile = (DialogueFile) topic.getToStage();
|
||||
dialogue.loadFile(topicFile);
|
||||
} else if(topic.getToStage() instanceof Integer) {
|
||||
int stage = (Integer) topic.getToStage();
|
||||
if (file == null) dialogue.stage = stage;
|
||||
else file.setStage(stage);
|
||||
}
|
||||
activeTopics.clear();
|
||||
|
||||
if (topic.getSkipPlayer())
|
||||
handle(componentId, buttonId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(file != null){
|
||||
player.dispatch(
|
||||
new DialogueOptionSelectionEvent(
|
||||
file,
|
||||
file.getStage(),
|
||||
buttonId - 1
|
||||
)
|
||||
);
|
||||
|
||||
file.handle(componentId,buttonId - 1);
|
||||
} else {
|
||||
player.dispatch(
|
||||
new DialogueOptionSelectionEvent(
|
||||
dialogue,
|
||||
dialogue.stage,
|
||||
buttonId - 1
|
||||
)
|
||||
);
|
||||
|
||||
dialogue.handle(componentId, buttonId - 1);//here
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the current dialogue.
|
||||
* @return {@code True} if successful.
|
||||
*/
|
||||
public boolean close() {
|
||||
if (dialogue != null) {
|
||||
actions.clear();
|
||||
|
||||
if (player.getInterfaceManager().getChatbox() != null && player.getInterfaceManager().getChatbox().getCloseEvent() != null) {
|
||||
return true;
|
||||
}
|
||||
if (dialogue != null) {
|
||||
DialoguePlugin d = dialogue;
|
||||
dialogue = null;
|
||||
d.close();
|
||||
player.dispatch(new DialogueCloseEvent(d));
|
||||
}
|
||||
}
|
||||
activeTopics.clear();
|
||||
return dialogue == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts a dialogue plugin on the mapping.
|
||||
* @param id The NPC id (or {@code 1 << 16 | dialogueId} when the dialogue
|
||||
* isn't for an NPC).
|
||||
* @param plugin The plugin.
|
||||
*/
|
||||
public static void add(int id, DialoguePlugin plugin) {
|
||||
if (PLUGINS.containsKey(id)) {
|
||||
throw new IllegalArgumentException("Dialogue " + (id & 0xFFFF) + " is already in use - [old=" + PLUGINS.get(id).getClass().getSimpleName() + ", new=" + plugin.getClass().getSimpleName() + "]!");
|
||||
}
|
||||
PLUGINS.put(id, plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send plain messages based on the amount of specified messages.
|
||||
* @param messages The messages.
|
||||
* @return The chatbox component.
|
||||
*/
|
||||
public Component sendDialogue(String... messages) {
|
||||
if (messages.length < 1 || messages.length > 4) {
|
||||
return null;
|
||||
}
|
||||
int interfaceId = 209 + messages.length;
|
||||
for (int i = 0; i < messages.length; i++) {
|
||||
player.getPacketDispatch().sendString(messages[i], interfaceId, i + 1);
|
||||
}
|
||||
player.getInterfaceManager().openChatbox(interfaceId);
|
||||
player.getPacketDispatch().sendInterfaceConfig(player.getInterfaceManager().getChatbox().getId(), 1, false);
|
||||
return player.getInterfaceManager().getChatbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a plane message and hides the continue button.
|
||||
* @param hideContinue if we should hide it or not.
|
||||
* @param messages the messages.
|
||||
* @return the component.
|
||||
*/
|
||||
public Component sendPlainMessage(final boolean hideContinue, String... messages) {
|
||||
sendDialogue(messages);
|
||||
player.getPacketDispatch().sendInterfaceConfig(player.getInterfaceManager().getChatbox().getId(), (messages.length + 1), hideContinue);
|
||||
return player.getInterfaceManager().getChatbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the destroy item chatbox interface.
|
||||
* @param id The item id.
|
||||
* @param message The message to display.
|
||||
* @return The component.
|
||||
*/
|
||||
public Component sendDestroyItem(int id, String message) {
|
||||
player.getInterfaceManager().openChatbox(94);
|
||||
PacketRepository.send(ContainerPacket.class, new ContainerContext(player, 94, 93, 93, new Item[] { new Item(id) }, 1, false));
|
||||
String text = ItemDefinition.forId(id).getConfiguration(ItemConfigParser.DESTROY_MESSAGE, "Are you sure you want to destroy this object?");
|
||||
if (text.length() > 200) {
|
||||
String[] words = text.split(" ");
|
||||
StringBuilder sb = new StringBuilder(words[0]);
|
||||
for (int i = 1; i < words.length; i++) {
|
||||
if (i == (words.length / 2)) {
|
||||
sb.append("<br>");
|
||||
} else {
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(words[i]);
|
||||
}
|
||||
text = sb.toString();
|
||||
}
|
||||
player.getPacketDispatch().sendString("Are you sure you want to destroy this object?", 94, 2);
|
||||
player.getPacketDispatch().sendString("Yes.", 94, 3);
|
||||
player.getPacketDispatch().sendString("No.", 94, 4);
|
||||
player.getPacketDispatch().sendString(text, 94, 7);
|
||||
player.getPacketDispatch().sendString(ItemDefinition.forId(id).getName(), 94, 8);
|
||||
return player.getInterfaceManager().getChatbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send plane messages with a blue title.
|
||||
* @param title The title.
|
||||
* @param messages The messages.
|
||||
* @return The chatbox component.
|
||||
*/
|
||||
public Component sendPlaneMessageWithBlueTitle(String title, String... messages) {
|
||||
player.getPacketDispatch().sendString(title, 372, 0);
|
||||
for (int i = 0; i < messages.length; i++) {
|
||||
player.getPacketDispatch().sendString(messages[i], 372, i + 1);
|
||||
}
|
||||
player.getInterfaceManager().openChatbox(372);
|
||||
return player.getInterfaceManager().getChatbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send plane messages with scroll and a blue title.
|
||||
* @param title The title.
|
||||
* @param messages The messages.
|
||||
* @return The chatbox component.
|
||||
*/
|
||||
public Component sendScrollMessageWithBlueTitle(String title, String... messages) {
|
||||
for (int i = 0; i < 11; i++) {
|
||||
player.getPacketDispatch().sendString(" ", 421, i + 2);
|
||||
}
|
||||
player.getPacketDispatch().sendString(title, 421, 1);
|
||||
for (int i = 0; i < messages.length; i++) {
|
||||
player.getPacketDispatch().sendString(messages[i], 421, i + 2);
|
||||
}
|
||||
player.getInterfaceManager().openChatbox(421);
|
||||
return player.getInterfaceManager().getChatbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message with an item next to it.
|
||||
* @param itemId The item id.
|
||||
*/
|
||||
public Component sendItemMessage(int itemId, String... messages) {
|
||||
player.getInterfaceManager().openChatbox(131);
|
||||
String message = messages[0];
|
||||
for (int i = 1; i < messages.length; i++) {
|
||||
message += "<br>" + messages[i];
|
||||
}
|
||||
player.getPacketDispatch().sendString(message, 131, 1);
|
||||
player.getPacketDispatch().sendItemOnInterface(itemId, 1, 131, 2);
|
||||
return player.getInterfaceManager().getChatbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message with an item next to it.
|
||||
*/
|
||||
public Component sendItemMessage(final Item item, String... messages) {
|
||||
player.getInterfaceManager().openChatbox(131);
|
||||
String message = messages[0];
|
||||
for (int i = 1; i < messages.length; i++) {
|
||||
message += "<br>" + messages[i];
|
||||
}
|
||||
player.getPacketDispatch().sendString(message, 131, 1);
|
||||
player.getPacketDispatch().sendItemOnInterface(item.getId(), item.getAmount(), 131, 2);
|
||||
return player.getInterfaceManager().getChatbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message with an item next to it.
|
||||
* @param message The message.
|
||||
*/
|
||||
public Component sendDoubleItemMessage(int first, int second, String message) {
|
||||
player.getInterfaceManager().openChatbox(131);
|
||||
player.getPacketDispatch().sendString(message, 131, 1);
|
||||
player.getPacketDispatch().sendItemOnInterface(first, 1, 131, 0);
|
||||
player.getPacketDispatch().sendItemOnInterface(second, 1, 131, 2);
|
||||
return player.getInterfaceManager().getChatbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message with an item next to it.
|
||||
* @param message The message.
|
||||
*/
|
||||
public Component sendDoubleItemMessage(Item first, Item second, String message) {
|
||||
player.getInterfaceManager().openChatbox(131);
|
||||
player.getPacketDispatch().sendString(message, 131, 1);
|
||||
player.getPacketDispatch().sendItemOnInterface(first.getId(), first.getAmount(), 131, 0);
|
||||
player.getPacketDispatch().sendItemOnInterface(second.getId(), second.getAmount(), 131, 2);
|
||||
return player.getInterfaceManager().getChatbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send dialogues based on the amount of specified messages.
|
||||
* @param entity The entity.
|
||||
* @param expression The entity's facial expression.
|
||||
* @param messages The messages.
|
||||
* @return The chatbox component.
|
||||
*/
|
||||
public Component sendDialogues(Entity entity, FacialExpression expression, String... messages) {
|
||||
return sendDialogues(entity, expression == null ? -1 : expression.getAnimationId(), messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send dialogues based on the amount of specified messages.
|
||||
* @param entity The entity.
|
||||
* @param expression The entity's facial expression.
|
||||
* @param messages The messages.
|
||||
* @return The chatbox component.
|
||||
*/
|
||||
public Component sendDialogues(Entity entity, int expression, String... messages) {
|
||||
return sendDialogues(entity instanceof Player ? -1 : ((NPC) entity).getShownNPC(player).getId(), expression, messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send dialogues based on the amount of specified messages.
|
||||
* @param npcId The npc id.
|
||||
* @param expression The entity's facial expression.
|
||||
* @param messages The messages.
|
||||
* @param hide the continue.
|
||||
* @return The chatbox component.
|
||||
*/
|
||||
public Component sendDialogues(int npcId, FacialExpression expression, boolean hide, String... messages) {
|
||||
sendDialogues(npcId, expression == null ? -1 : expression.getAnimationId(), messages);
|
||||
return player.getInterfaceManager().getChatbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send dialogues based on the amount of specified messages.
|
||||
* @param expression The entity's facial expression.
|
||||
* @param messages The messages.
|
||||
* @param hide the continue.
|
||||
* @return The chatbox component.
|
||||
*/
|
||||
public Component sendDialogues(Entity entity, FacialExpression expression, boolean hide, String... messages) {
|
||||
sendDialogues(entity, expression, messages);
|
||||
player.getPacketDispatch().sendInterfaceConfig(player.getInterfaceManager().getChatbox().getId(), 3, hide);
|
||||
return player.getInterfaceManager().getChatbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send dialogues based on the amount of specified messages.
|
||||
* @param expression The entity's facial expression.
|
||||
* @param messages The messages.
|
||||
* @param hide the continue.
|
||||
* @return The chatbox component.
|
||||
*/
|
||||
public Component sendDialogues(Entity entity, int expression, boolean hide, String... messages) {
|
||||
sendDialogues(entity, expression, messages);
|
||||
player.getPacketDispatch().sendInterfaceConfig(player.getInterfaceManager().getChatbox().getId(), 3, hide);
|
||||
return player.getInterfaceManager().getChatbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send dialogues based on the amount of specified messages.
|
||||
* @param expression The entity's facial expression.
|
||||
* @param messages The messages.
|
||||
* @return The chatbox component.
|
||||
*/
|
||||
public Component sendDialogues(int npcId, int expression, boolean hide, String... messages) {
|
||||
sendDialogues(npcId, expression, messages);
|
||||
player.getPacketDispatch().sendInterfaceConfig(player.getInterfaceManager().getChatbox().getId(), 3, hide);
|
||||
return player.getInterfaceManager().getChatbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send dialogues based on the amount of specified messages.
|
||||
* @param npcId The npc id.
|
||||
* @param expression The entity's facial expression.
|
||||
* @param messages The messages.
|
||||
* @return The chatbox component.
|
||||
*/
|
||||
public Component sendDialogues(int npcId, FacialExpression expression, String... messages) {
|
||||
return sendDialogues(npcId, expression == null ? -1 : expression.getAnimationId(), messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send dialogues based on the amount of specified messages.
|
||||
* @param npcId The npc id.
|
||||
* @param expression The entity's facial expression.
|
||||
* @param messages The messages.
|
||||
* @return The chatbox component.
|
||||
*/
|
||||
public Component sendDialogues(int npcId, int expression, String... messages) {
|
||||
if (messages.length < 1 || messages.length > 4) {
|
||||
return null;
|
||||
}
|
||||
boolean npc = npcId > -1;
|
||||
int interfaceId = (npc ? 240 : 63) + messages.length;
|
||||
if (expression == -1) {
|
||||
expression = FacialExpression.HALF_GUILTY.getAnimationId();
|
||||
}
|
||||
player.getPacketDispatch().sendAnimationInterface(expression, interfaceId, 2);
|
||||
if (npc) {
|
||||
player.getPacketDispatch().sendNpcOnInterface(npcId, interfaceId, 2);
|
||||
player.getPacketDispatch().sendString(NPCDefinition.forId(npcId).getName(), interfaceId, 3);
|
||||
} else {
|
||||
player.getPacketDispatch().sendPlayerOnInterface(interfaceId, 2);
|
||||
player.getPacketDispatch().sendString(player.getUsername(), interfaceId, 3);
|
||||
}
|
||||
for (int i = 0; i < messages.length; i++) {
|
||||
player.getPacketDispatch().sendString(messages[i].toString().replace("@name", player.getUsername()), interfaceId, (i + 4));
|
||||
}
|
||||
player.getInterfaceManager().openChatbox(interfaceId);
|
||||
player.getPacketDispatch().sendInterfaceConfig(player.getInterfaceManager().getChatbox().getId(), 3, false);
|
||||
return player.getInterfaceManager().getChatbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send options based on the amount of specified options.
|
||||
* @param title The title.
|
||||
* @param options The options.
|
||||
*/
|
||||
public Component sendOptions(Object title, String... options) {
|
||||
int interfaceId = 224 + (2 * options.length);
|
||||
if (options.length < 2 || options.length > 5) {
|
||||
return null;
|
||||
}
|
||||
if (title != null) {
|
||||
player.getPacketDispatch().sendString(title.toString(), interfaceId, 1);
|
||||
}
|
||||
for (int i = 0; i < options.length; i++) {
|
||||
player.getPacketDispatch().sendString(options[i].toString(), interfaceId, i + 2);
|
||||
}
|
||||
player.getInterfaceManager().openChatbox(interfaceId);
|
||||
return player.getInterfaceManager().getChatbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a input run script.
|
||||
* @param string The strings.
|
||||
* @param objects The arguments.
|
||||
*/
|
||||
public void sendInput(boolean string, Object... objects) {
|
||||
player.getPacketDispatch().sendRunScript(string ? 109 : 108, "s", objects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a long input.
|
||||
* @param objects the objects.
|
||||
*/
|
||||
public void sendLongInput(Object... objects) {
|
||||
player.getPacketDispatch().sendRunScript(110, "s", objects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the private message input.
|
||||
* @param reciever The receiver.
|
||||
*/
|
||||
public void sendMessageInput(String reciever) {
|
||||
player.getPacketDispatch().sendRunScript(107, "s", reciever);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the dialogue for the given id is added.
|
||||
* @param id The NPC id/dialogue id.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public static boolean contains(int id) {
|
||||
return PLUGINS.containsKey(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently opened dialogue.
|
||||
* @return The dialogue plugin.
|
||||
*/
|
||||
public DialoguePlugin getDialogue() {
|
||||
return dialogue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reserves a key for the name.
|
||||
* @param name The name.
|
||||
* @return The key.
|
||||
*/
|
||||
public static int getDialogueKey(String name) {
|
||||
return 1 << 16 | name.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a dialogue reward.
|
||||
* @param action the reward.
|
||||
*/
|
||||
public void addAction(DialogueAction action) {
|
||||
actions.add(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the actions.
|
||||
* @return The actions.
|
||||
*/
|
||||
public List<DialogueAction> getActions() {
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
package core.game.dialogue;
|
||||
|
||||
import core.game.component.Component;
|
||||
import core.game.node.entity.Entity;
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.plugin.Plugin;
|
||||
import core.plugin.PluginManifest;
|
||||
import core.plugin.PluginType;
|
||||
import core.tools.SystemLogger;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static core.api.DialUtilsKt.splitLines;
|
||||
import static core.tools.DialogueConstKt.DIALOGUE_INITIAL_OPTIONS_HANDLE;
|
||||
import static core.tools.DialogueConstKt.START_DIALOGUE;
|
||||
|
||||
/**
|
||||
* Represents a dialogue plugin.
|
||||
* @author Emperor
|
||||
*/
|
||||
@PluginManifest(type = PluginType.DIALOGUE)
|
||||
public abstract class DialoguePlugin implements Plugin<Player> {
|
||||
|
||||
/**
|
||||
* Represents the red string.
|
||||
*/
|
||||
protected static final String RED = "<col=8A0808>";
|
||||
|
||||
/**
|
||||
* Represents the blue string.
|
||||
*/
|
||||
protected static final String BLUE = "<col=08088A>";
|
||||
|
||||
/**
|
||||
* The player.
|
||||
*/
|
||||
protected Player player;
|
||||
|
||||
/**
|
||||
* The dialogue interpreter.
|
||||
*/
|
||||
protected DialogueInterpreter interpreter;
|
||||
|
||||
public DialogueFile file;
|
||||
|
||||
protected ArrayList<String> optionNames = new ArrayList<String>(10);
|
||||
protected ArrayList<DialogueFile> optionFiles = new ArrayList<DialogueFile>(10);
|
||||
|
||||
/**
|
||||
* Two options interface.
|
||||
*/
|
||||
protected final int TWO_OPTIONS = 228;
|
||||
|
||||
/**
|
||||
* Three options interface.
|
||||
*/
|
||||
protected final int THREE_OPTIONS = 230;
|
||||
|
||||
/**
|
||||
* Four options interface.
|
||||
*/
|
||||
protected final int FOUR_OPTIONS = 232;
|
||||
|
||||
/**
|
||||
* Five options interface.
|
||||
*/
|
||||
protected final int FIVE_OPTIONS = 234;
|
||||
|
||||
/**
|
||||
* The NPC the player is talking with.
|
||||
*/
|
||||
protected NPC npc;
|
||||
|
||||
/**
|
||||
* The current dialogue stage.
|
||||
*/
|
||||
public int stage;
|
||||
|
||||
/**
|
||||
* If the dialogue is finished.
|
||||
*/
|
||||
protected boolean finished;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code DialoguePlugin} {@code Object}.
|
||||
*/
|
||||
public DialoguePlugin() {
|
||||
/*
|
||||
* empty.
|
||||
*/
|
||||
}
|
||||
|
||||
public String pirateGender() {
|
||||
return (player.isMale() ? "lad" : "lass");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code DialoguePlugin} {@code Object}.
|
||||
* @param player The player.
|
||||
*/
|
||||
public DialoguePlugin(Player player) {
|
||||
this.player = player;
|
||||
if (player != null) {
|
||||
this.interpreter = player.getDialogueInterpreter();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this plugin.
|
||||
*/
|
||||
public void init() {
|
||||
for (int id : getIds()) {
|
||||
DialogueInterpreter.add(id, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes <b>(but does not end)</b> the dialogue.
|
||||
* @return {@code True} if the dialogue succesfully closed.
|
||||
*/
|
||||
public boolean close() {
|
||||
player.getInterfaceManager().closeChatbox();
|
||||
player.getInterfaceManager().openChatbox(137);
|
||||
if(file != null) file.end();
|
||||
finished = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void sendNormalDialogue(Entity entity, FacialExpression expression, String... messages) {
|
||||
interpreter.sendDialogues(entity, expression, messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the stage variable.
|
||||
*/
|
||||
public void increment() {
|
||||
stage++;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Increments the stage variable.
|
||||
* @return The stage variable.
|
||||
*/
|
||||
public int getAndIncrement() {
|
||||
return stage++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the dialogue.
|
||||
*/
|
||||
public void end() {
|
||||
if (interpreter != null) {
|
||||
interpreter.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
setStage(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fireEvent(String identifier, Object... args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DialoguePlugin newInstance(Player player) {
|
||||
try {
|
||||
Class<?> classReference = Class.forName(this.getClass().getCanonicalName());
|
||||
|
||||
return (DialoguePlugin)classReference
|
||||
.getDeclaredConstructor(Player.class)
|
||||
.newInstance(player);
|
||||
} catch (ClassNotFoundException
|
||||
| IllegalAccessException
|
||||
| IllegalArgumentException
|
||||
| NoSuchMethodException
|
||||
| InvocationTargetException
|
||||
| InstantiationException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the dialogue.
|
||||
* @param args The arguments.
|
||||
* @return {@code True} if the dialogue plugin succesfully opened.
|
||||
*/
|
||||
public boolean open(Object... args) {
|
||||
if (args.length > 0 && args[0] instanceof NPC) {
|
||||
npc = (NPC)args[0];
|
||||
}
|
||||
|
||||
if (npc == null) {
|
||||
SystemLogger.logWarn(this.getClass(),
|
||||
args[0].getClass().getSimpleName() +
|
||||
"Is not assigning an NPC. Whoever did that should fix it."
|
||||
);
|
||||
}
|
||||
|
||||
player.getDialogueInterpreter().handle(0, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the progress of this dialogue..
|
||||
* @return {@code True} if the dialogue has started.
|
||||
*/
|
||||
public abstract boolean handle(int interfaceId, int buttonId);
|
||||
|
||||
/**
|
||||
* Gets the ids of the NPCs using this dialogue plugin.
|
||||
* @return The array of NPC ids.
|
||||
*/
|
||||
public abstract int[] getIds();
|
||||
|
||||
/**
|
||||
* Method wrapper to send an npc dial.
|
||||
* @return the component.
|
||||
*/
|
||||
public Component npc(final String... messages) {
|
||||
if (npc == null) {
|
||||
return interpreter.sendDialogues(getIds()[0], getIds()[0] > 8591 ? FacialExpression.OLD_NORMAL : FacialExpression.FRIENDLY, messages);
|
||||
}
|
||||
return interpreter.sendDialogues(npc, npc.getId() > 8591 ? FacialExpression.OLD_NORMAL : FacialExpression.FRIENDLY, messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method wrapper to send an npc dial.
|
||||
* @param id the id.
|
||||
* @return the component.
|
||||
*/
|
||||
public Component npc(int id, final String... messages) {
|
||||
return interpreter.sendDialogues(id, FacialExpression.FRIENDLY, messages);
|
||||
}
|
||||
|
||||
public Component sendDialogue(String... messages) {
|
||||
return interpreter.sendDialogue(messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method wrapper to send an npc dial.
|
||||
* @return the component.
|
||||
*/
|
||||
public Component npc(FacialExpression expression, final String... messages) {
|
||||
if (npc == null) {
|
||||
return interpreter.sendDialogues(getIds()[0], expression, messages);
|
||||
}
|
||||
return interpreter.sendDialogues(npc, expression, messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method wrapper to send a player dial.
|
||||
* @return the component.
|
||||
*/
|
||||
public Component player(final String... messages) {
|
||||
return interpreter.sendDialogues(player, null, messages);
|
||||
}
|
||||
/**
|
||||
* Method wrapper to send a player dial.
|
||||
* @return the component.
|
||||
*/
|
||||
public Component player(FacialExpression expression, final String... messages) {
|
||||
return interpreter.sendDialogues(player, expression, messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to send options.
|
||||
* @param options the options.
|
||||
*/
|
||||
public void options(final String... options) {
|
||||
interpreter.sendOptions("Select an Option", options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the dialogue plugin is finished.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public boolean isFinished() {
|
||||
return finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player.
|
||||
* @return The player.
|
||||
*/
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the stage.
|
||||
* @param i the stage.
|
||||
*/
|
||||
public void setStage(int i) {
|
||||
this.stage = i;
|
||||
}
|
||||
|
||||
public void next() {
|
||||
this.stage += 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a DialogueFile and sets its stage to START_DIALOGUE, and diverts all further handling for the conversation to the file.
|
||||
* @param file the DialogueFile to load.
|
||||
*/
|
||||
public void loadFile(DialogueFile file){
|
||||
if(file == null) return;
|
||||
this.file = file.load(player,npc,interpreter);
|
||||
this.file.setDialoguePlugin(this);
|
||||
stage = START_DIALOGUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an option to the list of possible choices a player can pick from. Helps build the options interface for sendChoices()
|
||||
* @param name the name of the quest/activity to talk about. Turns into "Talk about $name" on the option interface.
|
||||
* @param file the DialogueFile that the option loads when selected.
|
||||
*/
|
||||
public void addOption(String name, DialogueFile file){
|
||||
optionNames.add("Talk about " + name);
|
||||
optionFiles.add(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the player a list of conversation options if there's more than one choice. I.E. multiple quest lines.
|
||||
* @return true if an options interface was sent, false if not.
|
||||
*/
|
||||
public boolean sendChoices(){
|
||||
if(optionNames.size() == 1){
|
||||
loadFile(optionFiles.get(0));
|
||||
return false;
|
||||
} else if(optionNames.isEmpty()) {
|
||||
stage = START_DIALOGUE;
|
||||
return false;
|
||||
} else {
|
||||
options(optionNames.toArray(new String[0]));
|
||||
stage = DIALOGUE_INITIAL_OPTIONS_HANDLE;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the automatic linesplitting feature in DialUtils to produce npc dialogues
|
||||
* @param expr the FacialExpression to use, located in the FacialExpression enum.
|
||||
* @param msg the message for the NPC to say
|
||||
*/
|
||||
public Component npcl(FacialExpression expr, String msg){
|
||||
return npc(expr, splitLines(msg, 54));
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the automatic linesplitting feature in DialUtils to produce player dialogues
|
||||
* @param expr the FacialExpression to use, located in the FacialExpression enum.
|
||||
* @param msg the message for the player to say
|
||||
*/
|
||||
public Component playerl(FacialExpression expr, String msg){
|
||||
return player(expr, splitLines(msg, 54));
|
||||
}
|
||||
|
||||
public void showTopics(Topic<?>... topics) {
|
||||
ArrayList<String> validTopics = new ArrayList<>();
|
||||
for(Topic<?> topic : topics)
|
||||
{
|
||||
if(topic instanceof IfTopic && !((IfTopic<?>) topic).getShowCondition()) continue;
|
||||
interpreter.activeTopics.add(topic);
|
||||
validTopics.add(topic.getText());
|
||||
}
|
||||
|
||||
if (validTopics.size() == 1) {
|
||||
Topic topic = interpreter.activeTopics.get(0);
|
||||
|
||||
if(topic.getToStage() instanceof DialogueFile) {
|
||||
DialogueFile topicFile = (DialogueFile) topic.getToStage();
|
||||
interpreter.getDialogue().loadFile(topicFile);
|
||||
} else if(topic.getToStage() instanceof Integer) {
|
||||
stage = (Integer) topic.getToStage();
|
||||
}
|
||||
|
||||
player(topic.getText());
|
||||
interpreter.activeTopics.clear();
|
||||
}
|
||||
|
||||
options(validTopics.toArray(new String[0]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package core.game.dialogue
|
||||
|
||||
/**
|
||||
* Topic/IfTopic system backported from my personal project
|
||||
* @author Ceikry
|
||||
*/
|
||||
open class Topic<T>(val expr: core.game.dialogue.FacialExpression, val text: String, val toStage: T, val skipPlayer: Boolean = false) {
|
||||
constructor(text: String, toStage: T, skipPlayer: Boolean = false) : this(core.game.dialogue.FacialExpression.ASKING, text, toStage, skipPlayer)
|
||||
}
|
||||
class IfTopic<T>(expr: core.game.dialogue.FacialExpression, text: String, toStage: T, val showCondition: Boolean, skipPlayer: Boolean = false) : Topic<T>(expr, text, toStage, skipPlayer) {
|
||||
constructor(text: String, toStage: T, showCondition: Boolean, skipPlayer: Boolean = false) : this(core.game.dialogue.FacialExpression.ASKING, text, toStage, showCondition, skipPlayer)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package core.game.dialogue
|
||||
|
||||
import core.game.node.entity.npc.NPC
|
||||
import core.game.node.entity.player.Player
|
||||
import core.tools.START_DIALOGUE
|
||||
|
||||
class EmptyPlugin(player: Player? = null,val file: DialogueFile?) : core.game.dialogue.DialoguePlugin(player) {
|
||||
override fun newInstance(player: Player?): core.game.dialogue.DialoguePlugin {
|
||||
return EmptyPlugin(player,null)
|
||||
}
|
||||
|
||||
override fun open(vararg args: Any?): Boolean {
|
||||
if(args.isNotEmpty() && args[0] is NPC){
|
||||
npc = args[0] as NPC
|
||||
}
|
||||
stage = START_DIALOGUE
|
||||
loadFile(file)
|
||||
interpreter.handle(0,0)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun handle(interfaceId: Int, buttonId: Int): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getIds(): IntArray {
|
||||
return intArrayOf(Integer.MAX_VALUE)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package core.game.dialogue;
|
||||
|
||||
/**
|
||||
* Represents the facial expressions (the animations the entity does when
|
||||
* talking).
|
||||
*
|
||||
* @author Emperor
|
||||
* @author Empathy
|
||||
*/
|
||||
public enum FacialExpression {
|
||||
|
||||
/**
|
||||
* The normal talking expression.
|
||||
*/
|
||||
|
||||
//Names based off https://2009scape.wiki/w/Chathead/Animations
|
||||
|
||||
//Chat heads from oldschool 2009scape?
|
||||
//Maybe for gnomes or dwarves? Chat heads are frozen when used on Human NPCs
|
||||
OLD_HAPPY(588),
|
||||
OLD_CALM_TALK1(589),
|
||||
OLD_CALM_TALK2(590),
|
||||
OLD_DEFAULT(591),
|
||||
OLD_EVIL1(592),
|
||||
OLD_EVIL2(593),
|
||||
OLD_NORMAL(594),
|
||||
OLD_SNEAKY(595),
|
||||
OLD_DISTRESSED(596),
|
||||
OLD_DISTRESSED2(597),
|
||||
OLD_ALMOST_CRYING(598),
|
||||
OLD_BOWS_HEAD_SAD(599),
|
||||
OLD_DRUNK_LEFT(600),
|
||||
OLD_DRUNK_RIGHT(601),
|
||||
OLD_NOT_INTERESTED(602),
|
||||
OLD_SLEEPY(603),
|
||||
OLD_PLAIN_EVIL(604),
|
||||
OLD_LAUGH1(605),
|
||||
OLD_LAUGH2(606),
|
||||
OLD_LAUGH3(607),
|
||||
OLD_LAUGH4(608),
|
||||
OLD_EVIL_LAUGH(609),
|
||||
OLD_SAD(610),
|
||||
OLD_MORE_SAD(611),
|
||||
OLD_ON_ONE_HAND(612),
|
||||
OLD_NEARLY_CRYING(613),
|
||||
OLD_ANGRY1(614),
|
||||
OLD_ANGRY2(615),
|
||||
OLD_ANGRY3(616),
|
||||
OLD_ANGRY4(617),
|
||||
|
||||
//Chatheads from 2009?
|
||||
NOD_YES(9741),
|
||||
WORRIED(9743),
|
||||
HALF_WORRIED(9745), //Not on the wiki, first half of worried
|
||||
AMAZED(9746),
|
||||
EXTREMELY_SHOCKED(9750),
|
||||
GUILTY(9758),
|
||||
HALF_GUILTY(9760), //Not on the wiki, first half of guilty
|
||||
SAD(9761),
|
||||
CRYING(9765),
|
||||
HALF_CRYING(9768), //Not on the wiki, but plays the first half of the crying animation
|
||||
AFRAID(9772),
|
||||
SCARED(9776),
|
||||
PANICKED(9780),
|
||||
ANNOYED(9784),
|
||||
ANGRY(9785),
|
||||
FURIOUS(9792),
|
||||
ANGRY_WITH_SMILE(9796), // Not on the wiki
|
||||
ANGRY_WITH_SMILE_AND_EVIL_EYE(9798), //Not on the Wiki
|
||||
SLEEPING(9802),
|
||||
SILENT(9804),
|
||||
NEUTRAL(9808),
|
||||
THINKING(9812),
|
||||
HALF_THINKING(9814),
|
||||
DISGUSTED(9816),
|
||||
DISGUSTED_HEAD_SHAKE(9823), //Not on the wiki
|
||||
ASKING(9827),
|
||||
HALF_ASKING(9830), //Not on wiki, first half of Asking
|
||||
ROLLING_EYES(9831), //9832, 9833 are the same
|
||||
HALF_ROLLING_EYES(9834), //Not on Wiki, first half of Rolling eyes
|
||||
DRUNK(9835),
|
||||
SUSPICIOUS(9836),
|
||||
LAUGH(9840),
|
||||
LOUDLY_LAUGHING(9841),
|
||||
EVIL_LAUGH(9842),
|
||||
FRIENDLY(9844),
|
||||
HAPPY(9847),
|
||||
JOLLY(9851),
|
||||
STRUGGLE(9865), //TODO: More?
|
||||
//9855-9857 are like disgusted? does it just repeat after this?
|
||||
|
||||
//Child Chathead?
|
||||
CHILD_EVIL_LAUGH(7171),
|
||||
CHILD_FRIENDLY(7172),
|
||||
CHILD_NORMAL(7173),
|
||||
CHILD_NEUTRAL(7174),
|
||||
CHILD_LOUDLY_LAUGHING(7175),
|
||||
CHILD_THINKING(7176),
|
||||
CHILD_SAD(7177),
|
||||
CHILD_GUILTY(7178),
|
||||
CHILD_SUSPICIOUS(7179); //TODO: More?
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The animation id.
|
||||
*/
|
||||
private final int animationId;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code FacialExpression} {@code Object}.
|
||||
*
|
||||
* @param animationId The animation id.
|
||||
*/
|
||||
FacialExpression(int animationId) {
|
||||
this.animationId = animationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the animation id.
|
||||
*
|
||||
* @return The animation id.
|
||||
*/
|
||||
public int getAnimationId() {
|
||||
return animationId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package core.game.dialogue;
|
||||
|
||||
import core.plugin.Initializable;
|
||||
import core.game.node.entity.player.Player;
|
||||
|
||||
/**
|
||||
* Represents a simple dialogue.
|
||||
* @author 'Vexia
|
||||
*/
|
||||
@Initializable
|
||||
public class SimpleDialoguePlugin extends DialoguePlugin {
|
||||
|
||||
public SimpleDialoguePlugin() {
|
||||
|
||||
}
|
||||
|
||||
public SimpleDialoguePlugin(Player player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getIds() {
|
||||
return new int[] { 70099 };
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(int interfaceId, int buttonId) {
|
||||
end();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DialoguePlugin newInstance(Player player) {
|
||||
return new SimpleDialoguePlugin(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open(Object... args) {
|
||||
String[] messages = new String[args.length];
|
||||
for (int i = 0; i < messages.length; i++)
|
||||
messages[i] = (String) args[i];
|
||||
interpreter.sendDialogue(messages);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package core.game.dialogue;
|
||||
|
||||
import core.plugin.Initializable;
|
||||
import core.game.node.entity.player.Player;
|
||||
|
||||
/**
|
||||
* Handles the SimpleEntityMessage dialogue.
|
||||
* @author 'Vexia
|
||||
*/
|
||||
@Initializable
|
||||
public class SimpleEntityMessage extends DialoguePlugin {
|
||||
|
||||
public SimpleEntityMessage() {
|
||||
|
||||
}
|
||||
|
||||
public SimpleEntityMessage(Player player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getIds() {
|
||||
return new int[] { 8000 };
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(int interfaceId, int buttonId) {
|
||||
end();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DialoguePlugin newInstance(Player player) {
|
||||
|
||||
return new SimpleEntityMessage(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open(Object... args) {
|
||||
String[] messages = new String[args.length];
|
||||
for (int i = 0; i < messages.length; i++)
|
||||
messages[i] = (String) args[i];
|
||||
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, messages);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
package core.game.dialogue
|
||||
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.item.Item
|
||||
import core.net.packet.PacketRepository
|
||||
import core.net.packet.context.ChildPositionContext
|
||||
import core.net.packet.out.RepositionChild
|
||||
import core.tools.StringUtils
|
||||
|
||||
/**
|
||||
* Represents a skill dialogue handler class.
|
||||
* @author Vexia
|
||||
*/
|
||||
open class SkillDialogueHandler(
|
||||
/**
|
||||
* Represents the player.
|
||||
*/
|
||||
val player: Player,
|
||||
/**
|
||||
* Represents the skill dialogue type.
|
||||
*/
|
||||
val type: SkillDialogue?, vararg data: Any) {
|
||||
/**
|
||||
* Gets the player.
|
||||
* @return The player.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets the type.
|
||||
* @return The type.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets the passed data.
|
||||
* @return the data.
|
||||
*/
|
||||
/**
|
||||
* Represents the object data passed through.
|
||||
*/
|
||||
val data: Array<Any>
|
||||
|
||||
/**
|
||||
* Method used to open a skill dialogue.
|
||||
*/
|
||||
fun open() {
|
||||
player.dialogueInterpreter.open(SKILL_DIALOGUE, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to display the content on the dialogue.
|
||||
*/
|
||||
fun display() {
|
||||
if (type == null) {
|
||||
player.debug("Error! Type is null.")
|
||||
return
|
||||
}
|
||||
type.display(player, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to create a product.
|
||||
* @param amount the amount.
|
||||
* @param index the index.
|
||||
*/
|
||||
open fun create(amount: Int, index: Int) {}
|
||||
|
||||
/**
|
||||
* Gets the total amount of items to be made.
|
||||
* @param index the index.
|
||||
* @return the amount.
|
||||
*/
|
||||
open fun getAll(index: Int): Int {
|
||||
return player.inventory.getAmount(data[0] as Item)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name.
|
||||
* @param item the item.
|
||||
* @return the name.
|
||||
*/
|
||||
protected open fun getName(item: Item): String {
|
||||
return StringUtils.formatDisplayName(item.name.replace("Unfired", ""))
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a skill dialogue type.
|
||||
* @author 'Vexia
|
||||
*/
|
||||
enum class SkillDialogue
|
||||
/**
|
||||
* Constructs a new `SkillDialogue` `Object`.
|
||||
* @param interfaceId the interface id.
|
||||
* @param base the base button.
|
||||
* @param length the length.
|
||||
*/(
|
||||
/**
|
||||
* Represents the interface id.
|
||||
*/
|
||||
val interfaceId: Int,
|
||||
/**
|
||||
* Represents the base button.
|
||||
*/
|
||||
private val baseButton: Int,
|
||||
/**
|
||||
* Represents the length.
|
||||
*/
|
||||
private val length: Int) {
|
||||
ONE_OPTION(309, 5, 1) {
|
||||
override fun display(player: Player, handler: SkillDialogueHandler) {
|
||||
val item = handler.data[0] as Item
|
||||
player.packetDispatch.sendString("<br><br><br><br>" + item.name, 309, 6)
|
||||
player.packetDispatch.sendItemZoomOnInterface(item.id, 160, 309, 2)
|
||||
PacketRepository.send(RepositionChild::class.java, ChildPositionContext(player, 309, 6, 60, 20))
|
||||
PacketRepository.send(RepositionChild::class.java, ChildPositionContext(player, 309, 2, 210, 30))
|
||||
}
|
||||
|
||||
override fun getAmount(handler: SkillDialogueHandler, buttonId: Int): Int {
|
||||
return when(buttonId){
|
||||
5 -> 1
|
||||
4 -> 5
|
||||
3 -> -1 //-1 is used to prompt an "enter an amount"
|
||||
else -> handler.getAll(getIndex(handler, buttonId))
|
||||
}
|
||||
}
|
||||
},
|
||||
TWO_OPTION(303, 7, 2) {
|
||||
override fun display(player: Player, handler: SkillDialogueHandler) {
|
||||
var item: Item
|
||||
player.interfaceManager.openChatbox(306)
|
||||
for (i in handler.data.indices) {
|
||||
item = handler.data[i] as Item
|
||||
player.packetDispatch.sendString("<br><br><br><br>" + handler.getName(item), 303, 7 + i)
|
||||
player.packetDispatch.sendItemZoomOnInterface(item.id, 160, 303, 2 + i)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getIndex(handler: SkillDialogueHandler?, buttonId: Int): Int {
|
||||
when (buttonId) {
|
||||
6, 5, 4, 3 -> return 0
|
||||
10, 9, 8, 7 -> return 1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
override fun getAmount(handler: SkillDialogueHandler, buttonId: Int): Int {
|
||||
when (buttonId) {
|
||||
6, 10 -> return 1
|
||||
5, 9 -> return 5
|
||||
4, 8 -> return 10
|
||||
3, 7 -> return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
},
|
||||
THREE_OPTION(304, 8, 3) {
|
||||
override fun display(player: Player, handler: SkillDialogueHandler) {
|
||||
var item: Item? = null
|
||||
for (i in 0..2) {
|
||||
item = handler.data[i] as Item
|
||||
player.packetDispatch.sendItemZoomOnInterface(item.id, 135, 304, 2 + i)
|
||||
player.packetDispatch.sendString("<br><br><br><br>" + item!!.name, 304, 304 - 296 + i * 4)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getIndex(handler: SkillDialogueHandler?, buttonId: Int): Int {
|
||||
when (buttonId) {
|
||||
7, 6, 5, 4 -> return 0
|
||||
11, 10, 9, 8 -> return 1
|
||||
15, 14, 13, 12 -> return 2
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
override fun getAmount(handler: SkillDialogueHandler, buttonId: Int): Int {
|
||||
when (buttonId) {
|
||||
7, 11, 15 -> return 1
|
||||
6, 10, 14 -> return 5
|
||||
5, 9, 13 -> return 10
|
||||
4, 8, 12 -> return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
},
|
||||
FOUR_OPTION(305, 9, 4) {
|
||||
override fun display(player: Player, handler: SkillDialogueHandler) {
|
||||
var item: Item? = null
|
||||
for (i in 0..3) {
|
||||
item = handler.data[i] as Item
|
||||
player.packetDispatch.sendItemZoomOnInterface(item!!.id, 135, 305, 2 + i)
|
||||
player.packetDispatch.sendString("<br><br><br><br>" + item.name, 305, 305 - 296 + i * 4)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getIndex(handler: SkillDialogueHandler?, buttonId: Int): Int {
|
||||
when (buttonId) {
|
||||
5, 8, 6, 7 -> return 0
|
||||
9, 10, 11, 12 -> return 1
|
||||
13, 14, 15, 16 -> return 2
|
||||
17, 18, 19, 20 -> return 3
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun getAmount(handler: SkillDialogueHandler, buttonId: Int): Int {
|
||||
when (buttonId) {
|
||||
8, 12, 16, 20 -> return 1
|
||||
7, 11, 15, 19 -> return 5
|
||||
6, 10, 14, 18 -> return 10
|
||||
5, 9, 13, 17 -> return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
},
|
||||
FIVE_OPTION(306, 7, 5) {
|
||||
/**
|
||||
* Represents the position data.
|
||||
*/
|
||||
private val positions = arrayOf(intArrayOf(10, 30), intArrayOf(117, 10), intArrayOf(217, 20), intArrayOf(317, 15), intArrayOf(408, 15))
|
||||
override fun display(player: Player, handler: SkillDialogueHandler) {
|
||||
var item: Item
|
||||
player.interfaceManager.openChatbox(306)
|
||||
for (i in handler.data.indices) {
|
||||
item = handler.data[i] as Item
|
||||
player.packetDispatch.sendString("<br><br><br><br>" + handler.getName(item), 306, 10 + 4 * i)
|
||||
player.packetDispatch.sendItemZoomOnInterface(item.id, 160, 306, 2 + i)
|
||||
PacketRepository.send(RepositionChild::class.java, ChildPositionContext(player, 306, 2 + i, positions[i][0], positions[i][1]))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getIndex(handler: SkillDialogueHandler?, buttonId: Int): Int {
|
||||
when (buttonId) {
|
||||
9, 8, 7, 6 -> return 0
|
||||
13, 12, 11, 10 -> return 1
|
||||
17, 16, 15, 14 -> return 2
|
||||
21, 20, 19, 18 -> return 3
|
||||
25, 24, 23, 22 -> return 4
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun getAmount(handler: SkillDialogueHandler, buttonId: Int): Int {
|
||||
when (buttonId) {
|
||||
9, 13, 17, 21, 25 -> return 1
|
||||
8, 12, 16, 20, 24 -> return 5
|
||||
7, 11, 15, 19, 23 -> return 10
|
||||
6, 10, 14, 18, 22 -> return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the interfaceId.
|
||||
* @return The interfaceId.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Method used to display the content for this type.
|
||||
* @param player the player.
|
||||
* @param handler the handler.
|
||||
*/
|
||||
open fun display(player: Player, handler: SkillDialogueHandler) {}
|
||||
|
||||
/**
|
||||
* Gets the amount.
|
||||
* @param handler the handler.
|
||||
* @param buttonId the buttonId.
|
||||
* @return the amount.
|
||||
*/
|
||||
open fun getAmount(handler: SkillDialogueHandler, buttonId: Int): Int {
|
||||
for (k in 0..3) {
|
||||
for (i in 0 until length) {
|
||||
val `val` = baseButton - k + 4 * i
|
||||
if (`val` == buttonId) {
|
||||
return if (k == 13) 1 else if (k == 8) 5 else if (k == 7) 10 else 6
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index selected.
|
||||
* @param handler the handler.
|
||||
* @param buttonId the buttonId.
|
||||
* @return the index selected.
|
||||
*/
|
||||
open fun getIndex(handler: SkillDialogueHandler?, buttonId: Int): Int {
|
||||
var index = 0
|
||||
for (k in 0..3) {
|
||||
for (i in 1 until length) {
|
||||
val `val` = baseButton + k + 4 * i
|
||||
if (`val` == buttonId) {
|
||||
return index + 1
|
||||
} else if (`val` <= buttonId) {
|
||||
index++
|
||||
}
|
||||
}
|
||||
index = 0
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Gets the type for the length.
|
||||
* @param length2 the length to compare.
|
||||
* @return the type.
|
||||
*/
|
||||
fun forLength(length2: Int): SkillDialogue? {
|
||||
for (dial in values()) {
|
||||
if (dial.length == length2) {
|
||||
return dial
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Represents the skill dialogue id.
|
||||
*/
|
||||
const val SKILL_DIALOGUE = 3 shl 16
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new `SkillDialogueHandler` `Object`.
|
||||
* @param player the player.
|
||||
* @param type the type.
|
||||
* @param data the data.
|
||||
*/
|
||||
init {
|
||||
this.data = data as Array<Any>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package core.game.dialogue;
|
||||
|
||||
import static core.api.ContentAPIKt.*;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.plugin.Initializable;
|
||||
import kotlin.Unit;
|
||||
|
||||
/**
|
||||
* Represents the dialogue plugin used to automatically handle skill dialogues with creation amounts.
|
||||
* @author Vexia
|
||||
*
|
||||
*/
|
||||
@Initializable
|
||||
public class SkillDialoguePlugin extends DialoguePlugin {
|
||||
|
||||
/**
|
||||
* Represents the skill dialogue handler.
|
||||
*/
|
||||
private SkillDialogueHandler handler;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code SkillDialogue} {@code Object}.
|
||||
*/
|
||||
public SkillDialoguePlugin() {
|
||||
/**
|
||||
* empty.
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code SkillDialogue} {@code Object}.
|
||||
* @param player the player.
|
||||
*/
|
||||
public SkillDialoguePlugin(final Player player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DialoguePlugin newInstance(Player player) {
|
||||
return new SkillDialoguePlugin(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open(Object... args) {
|
||||
handler = (SkillDialogueHandler) args[0];
|
||||
handler.display();
|
||||
player.getInterfaceManager().openChatbox(handler.getType().getInterfaceId());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(int interfaceId, int buttonId) {
|
||||
final int amount = handler.getType().getAmount(handler, buttonId);
|
||||
final int index = handler.getType().getIndex(handler, buttonId);
|
||||
end();
|
||||
if (amount != -1) {
|
||||
handler.create(amount, index);
|
||||
} else {
|
||||
sendInputDialogue(player, true, "Enter the amount:", (value) -> {
|
||||
if (value instanceof String) {
|
||||
handler.create(Integer.parseInt((String) value), index);
|
||||
} else {
|
||||
handler.create((int) value, index);
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getIds() {
|
||||
return new int[] { SkillDialogueHandler.SKILL_DIALOGUE };
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package core.game.dialogue.book;
|
||||
|
||||
import core.game.component.Component;
|
||||
import core.game.dialogue.DialoguePlugin;
|
||||
import core.game.dialogue.DialoguePlugin;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.plugin.PluginManifest;
|
||||
import core.plugin.PluginType;
|
||||
|
||||
/**
|
||||
* Represents a dialogue book.
|
||||
* @author 'Vexia
|
||||
* @date 1/1/14
|
||||
*/
|
||||
@PluginManifest(type = PluginType.DIALOGUE)
|
||||
public abstract class Book extends DialoguePlugin {
|
||||
|
||||
/**
|
||||
* Represents the component interface of the book.
|
||||
*/
|
||||
private static final Component INTERFACE = new Component(49);
|
||||
|
||||
/**
|
||||
* Represents the red string.
|
||||
*/
|
||||
protected static final String RED = "<col=8A0808>";
|
||||
|
||||
/**
|
||||
* Represents the blue string.
|
||||
*/
|
||||
protected static final String BLUE = "<col=08088A>";
|
||||
|
||||
/**
|
||||
* Represents the name of the book.
|
||||
*/
|
||||
protected String name;
|
||||
|
||||
/**
|
||||
* Represents the id of this book.
|
||||
*/
|
||||
protected int id;
|
||||
|
||||
/**
|
||||
* Represents the pages of this book.
|
||||
*/
|
||||
protected PageSet[] sets;
|
||||
|
||||
/**
|
||||
* Represents the index of the page set we're at.
|
||||
*/
|
||||
protected int index = -1;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Book} {@code Object}.
|
||||
* @param name the name.
|
||||
* @param id the id.
|
||||
*/
|
||||
public Book(final Player player, final String name, final int id, final PageSet... sets) {
|
||||
this.player = player;
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
this.sets = sets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Book} {@code Object}.
|
||||
*/
|
||||
public Book() {
|
||||
/**
|
||||
* empty.
|
||||
*/
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open(Object... args) {
|
||||
return open(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(int interfaceId, int buttonId) {
|
||||
switch (buttonId) {
|
||||
case 112:
|
||||
player.getInterfaceManager().close();
|
||||
break;
|
||||
case 66:
|
||||
case 52:
|
||||
next();
|
||||
break;
|
||||
case 64:
|
||||
case 50:
|
||||
previous();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fireEvent(String identifier, Object... args) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to open a book.
|
||||
* @return <code>True</code> if succesfully opened.
|
||||
*/
|
||||
public boolean open(final Player player) {
|
||||
next();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to write the next dialogue.
|
||||
*/
|
||||
public void next() {
|
||||
player.lock();
|
||||
index++;
|
||||
if (index > sets.length - 1) {
|
||||
player.unlock();
|
||||
player.getInterfaceManager().close();
|
||||
return;
|
||||
}
|
||||
final Page[] set = sets[index].getPages();
|
||||
display(set);
|
||||
player.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to write the previous dialogue.
|
||||
*/
|
||||
public void previous() {
|
||||
player.lock();
|
||||
index--;
|
||||
if (index < 0) {
|
||||
index = 0;
|
||||
}
|
||||
final Page[] set = sets[index].getPages();
|
||||
display(set);
|
||||
player.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to display a set of pages.
|
||||
* @param set the set.
|
||||
*/
|
||||
public void display(Page[] set) {
|
||||
// TODO: It seems like different subclasses instantiate this by
|
||||
// copy-paste, this should probably be made final, and should switch between a few
|
||||
// implementations by component id
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used when the book is finished.
|
||||
*/
|
||||
public void finish() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name.
|
||||
* @return The name.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the id.
|
||||
* @return The id.
|
||||
*/
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index.
|
||||
* @return The index.
|
||||
*/
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index.
|
||||
* @param index The index to set.
|
||||
*/
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the interface component.
|
||||
* @return the component.
|
||||
*/
|
||||
public Component getInterface() {
|
||||
return INTERFACE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package core.game.dialogue.book;
|
||||
|
||||
/**
|
||||
* Represents a book line.
|
||||
* @author 'Vexia
|
||||
* @date 31/12/2013
|
||||
*/
|
||||
public class BookLine {
|
||||
|
||||
/**
|
||||
* Represents the message to display.
|
||||
*/
|
||||
private final String message;
|
||||
|
||||
/**
|
||||
* Represents the child if of the line.
|
||||
*/
|
||||
private final int child;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Page} {@code Object}.
|
||||
* @param message the message.
|
||||
* @param child the child.
|
||||
*/
|
||||
public BookLine(final String message, final int child) {
|
||||
this.message = message;
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message.
|
||||
* @return The message.
|
||||
*/
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the child.
|
||||
* @return The child.
|
||||
*/
|
||||
public int getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package core.game.dialogue.book;
|
||||
|
||||
/**
|
||||
* Represents a page on a book.
|
||||
* @author 'Vexia
|
||||
* @date 1/1/14
|
||||
*/
|
||||
public class Page {
|
||||
|
||||
/**
|
||||
* Represents the lines on a page.
|
||||
*/
|
||||
private final BookLine[] lines;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Page} {@code Object}.
|
||||
* @param lines the lines.
|
||||
*/
|
||||
public Page(BookLine... lines) {
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the lines.
|
||||
* @return The lines.
|
||||
*/
|
||||
public BookLine[] getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package core.game.dialogue.book;
|
||||
|
||||
/**
|
||||
* Represents a set of pages on a book.
|
||||
* @author 'Vexia
|
||||
*/
|
||||
public class PageSet {
|
||||
|
||||
/**
|
||||
* Represents the set of pages.
|
||||
*/
|
||||
private final Page[] pages;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code PageSet} {@code Object}.
|
||||
* @param pages the pages.
|
||||
*/
|
||||
public PageSet(final Page... pages) {
|
||||
this.pages = pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pages.
|
||||
* @return The pages.
|
||||
*/
|
||||
public Page[] getPages() {
|
||||
return pages;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package core.game.diary
|
||||
|
||||
import core.api.inBorders
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.world.map.zone.ZoneBorders
|
||||
|
||||
class AreaDiaryTask(
|
||||
val zoneBorders: ZoneBorders,
|
||||
val diaryLevel: DiaryLevel,
|
||||
val taskId: Int,
|
||||
private val condition: ((player: Player) -> Boolean)? = null
|
||||
) {
|
||||
fun whenSatisfied(player: Player, then: () -> Unit) {
|
||||
var result = inBorders(player, zoneBorders)
|
||||
|
||||
condition?.let {
|
||||
result = it(player)
|
||||
}
|
||||
|
||||
if (result) then()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package core.game.diary
|
||||
|
||||
import core.api.*
|
||||
import core.game.event.*
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.entity.player.link.diary.DiaryType
|
||||
import core.game.world.map.zone.ZoneBorders
|
||||
import core.api.Event
|
||||
|
||||
abstract class DiaryEventHookBase(private val diaryType: DiaryType) : MapArea, LoginListener {
|
||||
protected companion object {
|
||||
private fun <T> forEligibleEntityDo(entity: Entity, event: T, handler: (Player, T) -> Unit) {
|
||||
if (entity !is Player) return
|
||||
if (entity.isArtificial) return
|
||||
|
||||
handler(entity, event)
|
||||
}
|
||||
}
|
||||
|
||||
class EventHandler<T : core.game.event.Event>(
|
||||
private val owner: DiaryEventHookBase,
|
||||
private val handler: (Player, T) -> Unit
|
||||
) : EventHook<T> {
|
||||
override fun process(entity: Entity, event: T) {
|
||||
forEligibleEntityDo(entity, event, handler)
|
||||
}
|
||||
}
|
||||
|
||||
open val areaTasks get() = arrayOf<AreaDiaryTask>()
|
||||
|
||||
final override fun defineAreaBorders(): Array<ZoneBorders>
|
||||
= areaTasks.map { task -> task.zoneBorders }.toTypedArray()
|
||||
|
||||
final override fun areaEnter(entity: Entity) {
|
||||
if (entity !is Player) return
|
||||
if (entity.isArtificial) return
|
||||
|
||||
onAreaVisited(entity)
|
||||
}
|
||||
|
||||
final override fun areaLeave(entity: Entity, logout: Boolean) {
|
||||
if (entity !is Player) return
|
||||
if (entity.isArtificial) return
|
||||
|
||||
onAreaLeft(entity)
|
||||
}
|
||||
|
||||
final override fun login(player: Player) {
|
||||
player.hook(Event.ResourceProduced, EventHandler(this, ::onResourceProduced))
|
||||
player.hook(Event.NPCKilled, EventHandler(this, ::onNpcKilled))
|
||||
player.hook(Event.Teleported, EventHandler(this, ::onTeleported))
|
||||
player.hook(Event.FireLit, EventHandler(this, ::onFireLit))
|
||||
player.hook(Event.Interacted, EventHandler(this, ::onInteracted))
|
||||
player.hook(Event.ButtonClicked, EventHandler(this, ::onButtonClicked))
|
||||
player.hook(Event.DialogueOpened, EventHandler(this, ::onDialogueOpened))
|
||||
player.hook(Event.DialogueOptionSelected, EventHandler(this, ::onDialogueOptionSelected))
|
||||
player.hook(Event.DialogueClosed, EventHandler(this, ::onDialogueClosed))
|
||||
player.hook(Event.UsedWith, EventHandler(this, ::onUsedWith))
|
||||
player.hook(Event.PickedUp, EventHandler(this, ::onPickedUp))
|
||||
player.hook(Event.InterfaceOpened, EventHandler(this, ::onInterfaceOpened))
|
||||
player.hook(Event.AttributeSet, EventHandler(this, ::onAttributeSet))
|
||||
player.hook(Event.AttributeRemoved, EventHandler(this, ::onAttributeRemoved))
|
||||
player.hook(Event.SpellCast, EventHandler(this, ::onSpellCast))
|
||||
player.hook(Event.ItemAlchemized, EventHandler(this, ::onItemAlchemized))
|
||||
player.hook(Event.ItemEquipped, EventHandler(this, ::onItemEquipped))
|
||||
player.hook(Event.ItemUnequipped, EventHandler(this, ::onItemUnequipped))
|
||||
player.hook(Event.ItemPurchased, EventHandler(this, ::onItemPurchasedFromShop))
|
||||
player.hook(Event.ItemSold, EventHandler(this, ::onItemSoldToShop))
|
||||
player.hook(Event.JobAssigned, EventHandler(this, ::onJobAssigned))
|
||||
player.hook(Event.FairyRingDialed, EventHandler(this, ::onFairyRingDialed))
|
||||
player.hook(Event.SummoningPointsRecharged, EventHandler(this, ::onSummoningPointsRecharged))
|
||||
player.hook(Event.PrayerPointsRecharged, EventHandler(this, ::onPrayerPointsRecharged))
|
||||
}
|
||||
|
||||
protected fun fulfillTaskRequirement(player: Player, level: DiaryLevel, task: Int, attribute: String) {
|
||||
if (getAttribute(player, attribute, false)) return
|
||||
|
||||
player.achievementDiaryManager.updateTask(
|
||||
player,
|
||||
diaryType,
|
||||
findIndexFor(level),
|
||||
task,
|
||||
false
|
||||
)
|
||||
|
||||
setAttribute(player, "/save:$attribute", true)
|
||||
}
|
||||
|
||||
protected fun whenTaskRequirementFulfilled(player: Player, attribute: String, then: () -> Unit) {
|
||||
if (getAttribute(player, attribute, false)) {
|
||||
then()
|
||||
removeAttribute(player, attribute)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun progressIncrementalTask(
|
||||
player: Player,
|
||||
level: DiaryLevel,
|
||||
task: Int,
|
||||
attribute: String,
|
||||
maxProgress: Int
|
||||
) {
|
||||
if (isTaskCompleted(player, level, task)) return
|
||||
|
||||
val newValue = getAttribute(player, attribute, 0) + 1
|
||||
|
||||
setAttribute(
|
||||
player,
|
||||
"/save:${attribute}",
|
||||
newValue
|
||||
)
|
||||
|
||||
if (newValue < maxProgress) {
|
||||
player.achievementDiaryManager.updateTask(
|
||||
player,
|
||||
diaryType,
|
||||
findIndexFor(level),
|
||||
task,
|
||||
false
|
||||
)
|
||||
} else {
|
||||
finishTask(player, level, task)
|
||||
removeAttribute(player, attribute)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun progressFlaggedTask(
|
||||
player: Player,
|
||||
level: DiaryLevel,
|
||||
task: Int,
|
||||
attribute: String,
|
||||
bit: Int,
|
||||
targetValue: Int
|
||||
) {
|
||||
if (isTaskCompleted(player, level, task)) {
|
||||
return
|
||||
}
|
||||
|
||||
val oldValue = getAttribute(player, attribute, 0)
|
||||
val newValue = oldValue or bit
|
||||
|
||||
if (newValue != targetValue) {
|
||||
if ((oldValue and bit) != 0) return
|
||||
|
||||
setAttribute(
|
||||
player,
|
||||
"/save:${attribute}",
|
||||
newValue
|
||||
)
|
||||
|
||||
player.achievementDiaryManager.updateTask(
|
||||
player,
|
||||
diaryType,
|
||||
findIndexFor(level),
|
||||
task,
|
||||
false
|
||||
)
|
||||
} else {
|
||||
finishTask(player, level, task)
|
||||
removeAttribute(player, attribute)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun finishTask(player: Player, level: DiaryLevel, task: Int) {
|
||||
player.achievementDiaryManager.finishTask(
|
||||
player,
|
||||
diaryType,
|
||||
findIndexFor(level),
|
||||
task
|
||||
)
|
||||
}
|
||||
|
||||
private fun isTaskCompleted(player: Player, level: DiaryLevel, task: Int): Boolean {
|
||||
return player.achievementDiaryManager.hasCompletedTask(
|
||||
diaryType,
|
||||
findIndexFor(level),
|
||||
task
|
||||
)
|
||||
}
|
||||
|
||||
private fun findIndexFor(level: DiaryLevel): Int {
|
||||
val levelName = level.name.lowercase().replaceFirstChar { c -> c.uppercase() }
|
||||
val levelIndex = diaryType.levelNames.indexOf(levelName)
|
||||
|
||||
if (levelIndex < 0) {
|
||||
throw IllegalArgumentException("'$levelName' was not found in diary '$diaryType'.")
|
||||
}
|
||||
|
||||
return levelIndex
|
||||
}
|
||||
|
||||
protected open fun onAreaVisited(player: Player) {
|
||||
areaTasks.forEach {
|
||||
it.whenSatisfied(player) {
|
||||
finishTask(
|
||||
player,
|
||||
it.diaryLevel,
|
||||
it.taskId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun onAreaLeft(player: Player) {}
|
||||
protected open fun onResourceProduced(player: Player, event: ResourceProducedEvent) {}
|
||||
protected open fun onNpcKilled(player: Player, event: NPCKillEvent) {}
|
||||
protected open fun onTeleported(player: Player, event: TeleportEvent) {}
|
||||
protected open fun onFireLit(player: Player, event: LitFireEvent) {}
|
||||
protected open fun onInteracted(player: Player, event: InteractionEvent) {}
|
||||
protected open fun onButtonClicked(player: Player, event: ButtonClickEvent) {}
|
||||
protected open fun onDialogueOpened(player: Player, event: DialogueOpenEvent) {}
|
||||
protected open fun onDialogueClosed(player: Player, event: DialogueCloseEvent) {}
|
||||
protected open fun onDialogueOptionSelected(player: Player, event: DialogueOptionSelectionEvent) {}
|
||||
protected open fun onUsedWith(player: Player, event: UseWithEvent) {}
|
||||
protected open fun onPickedUp(player: Player, event: PickUpEvent) {}
|
||||
protected open fun onInterfaceOpened(player: Player, event: InterfaceOpenEvent) {}
|
||||
protected open fun onAttributeSet(player: Player, event: AttributeSetEvent) {}
|
||||
protected open fun onAttributeRemoved(player: Player, event: AttributeRemoveEvent) {}
|
||||
protected open fun onSpellCast(player: Player, event: SpellCastEvent) {}
|
||||
protected open fun onItemAlchemized(player: Player, event: ItemAlchemizationEvent) {}
|
||||
protected open fun onItemEquipped(player: Player, event: ItemEquipEvent) {}
|
||||
protected open fun onItemUnequipped(player: Player, event: ItemUnequipEvent) {}
|
||||
protected open fun onItemPurchasedFromShop(player: Player, event: ItemShopPurchaseEvent) {}
|
||||
protected open fun onItemSoldToShop(player: Player, event: ItemShopSellEvent) {}
|
||||
protected open fun onJobAssigned(player: Player, event: JobAssignmentEvent) {}
|
||||
protected open fun onFairyRingDialed(player: Player, event: FairyRingDialEvent) {}
|
||||
protected open fun onSummoningPointsRecharged(player: Player, event: SummoningPointsRechargeEvent) {}
|
||||
protected open fun onPrayerPointsRecharged(player: Player, event: PrayerPointsRechargeEvent) {}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package core.game.diary
|
||||
|
||||
enum class DiaryLevel {
|
||||
BEGINNER,
|
||||
EASY,
|
||||
MEDIUM,
|
||||
HARD
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package core.game.event
|
||||
|
||||
interface Event {}
|
||||
@@ -0,0 +1,7 @@
|
||||
package core.game.event
|
||||
|
||||
import core.game.node.entity.Entity
|
||||
|
||||
interface EventHook<T: Event> {
|
||||
fun process(entity: Entity, event: T)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package core.game.event
|
||||
|
||||
import core.game.component.Component
|
||||
import core.game.dialogue.DialoguePlugin
|
||||
import core.game.node.Node
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.node.entity.npc.NPC
|
||||
import core.game.node.entity.player.link.SpellBookManager.SpellBook
|
||||
import core.game.node.entity.player.link.TeleportManager.TeleportType
|
||||
import core.game.node.item.Item
|
||||
import core.game.world.map.Location
|
||||
import content.global.activity.jobs.JobType
|
||||
import content.global.handlers.iface.FairyRing
|
||||
import content.global.skill.magic.TeleportMethod
|
||||
|
||||
data class ResourceProducedEvent(val itemId: Int, val amount: Int, val source: Node, val original: Int = -1) : Event
|
||||
data class NPCKillEvent(val npc: NPC) : Event
|
||||
data class TeleportEvent(val type: TeleportType, val method: TeleportMethod, val source: Any, val location: Location) : Event
|
||||
data class LitFireEvent(val logId: Int) : Event
|
||||
data class InteractionEvent(val target: Node, val option: String) : Event
|
||||
data class ButtonClickEvent(val iface: Int, val buttonId: Int) : Event
|
||||
data class DialogueOpenEvent(val dialogue: DialoguePlugin) : Event
|
||||
data class DialogueOptionSelectionEvent(val dialogue: Any, val currentStage: Int, val optionId: Int) : Event
|
||||
data class DialogueCloseEvent(val dialogue: DialoguePlugin) : Event
|
||||
data class UseWithEvent(val used: Int, val with: Int) : Event
|
||||
data class SelfDeathEvent(val killer: Entity) : Event
|
||||
data class TickEvent(val worldTicks: Int) : Event
|
||||
data class PickUpEvent(val itemId: Int) : Event
|
||||
data class InterfaceOpenEvent(val component: Component) : Event
|
||||
data class InterfaceCloseEvent(val component: Component) : Event
|
||||
data class AttributeSetEvent(val entity: Entity, val attribute: String, val value: Any) : Event
|
||||
data class AttributeRemoveEvent(val entity: Entity, val attribute: String) : Event
|
||||
data class SpellCastEvent(val spellBook: SpellBook, val spellId: Int) : Event
|
||||
data class ItemAlchemizationEvent(val itemId: Int, val isHigh: Boolean) : Event
|
||||
data class ItemEquipEvent(val itemId: Int, val slotId: Int) : Event
|
||||
data class ItemUnequipEvent(val itemId: Int, val slotId: Int) : Event
|
||||
data class ItemShopPurchaseEvent(val itemId: Int, val amount: Int, val currency: Item) : Event
|
||||
data class ItemShopSellEvent(val itemId: Int, val amount: Int, val currency: Item) : Event
|
||||
data class JobAssignmentEvent(val jobType: JobType, val employerNpc: NPC) : Event
|
||||
data class FairyRingDialEvent(val fairyRing: FairyRing) : Event
|
||||
data class VarbitUpdateEvent(val offset: Int, val value: Int) : Event
|
||||
data class DynamicSkillLevelChangeEvent(val skillId: Int, val oldValue: Int, val newValue: Int): Event
|
||||
data class SummoningPointsRechargeEvent(val obelisk: Node) : Event
|
||||
data class PrayerPointsRechargeEvent(val altar: Node) : Event
|
||||
data class XPGainEvent(val skillId: Int, val amount: Double) : Event
|
||||
@@ -0,0 +1,45 @@
|
||||
package core.game.ge
|
||||
|
||||
import core.cache.def.impl.ItemDefinition
|
||||
import org.rs09.consts.Items
|
||||
|
||||
class BotPrices {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getPrice(id: Int): Int {
|
||||
return getPriceOverrides(id) ?: ItemDefinition.forId(id).value
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun getPriceOverrides(id: Int): Int? {
|
||||
return when (id) {
|
||||
Items.PURE_ESSENCE_7936 -> 50
|
||||
Items.BOW_STRING_1777 -> 250
|
||||
Items.MAGIC_LOGS_1513 -> 750
|
||||
Items.COWHIDE_1739 -> 250
|
||||
Items.DRAGON_BONES_536 -> 1250
|
||||
Items.GREEN_DRAGONHIDE_1753 -> 550
|
||||
Items.GRIMY_RANARR_207 -> 1214
|
||||
Items.GRIMY_AVANTOE_211 -> 453
|
||||
Items.GRIMY_CADANTINE_215 -> 232
|
||||
Items.GRIMY_DWARF_WEED_217 -> 86
|
||||
Items.GRIMY_GUAM_199 -> 50
|
||||
Items.GRIMY_HARRALANDER_205 -> 115
|
||||
Items.GRIMY_IRIT_209 -> 860
|
||||
Items.GRIMY_KWUARM_213 -> 334
|
||||
Items.GRIMY_LANTADYME_2485 -> 115
|
||||
Items.GRIMY_MARRENTILL_201 -> 250
|
||||
Items.LOBSTER_379 -> 268
|
||||
Items.RAW_LOBSTER_377 -> 265
|
||||
Items.LOOP_HALF_OF_A_KEY_987 -> 5250
|
||||
Items.TOOTH_HALF_OF_A_KEY_985 -> 4263
|
||||
Items.SWORDFISH_373 -> 400
|
||||
Items.RAW_SWORDFISH_371 -> 390
|
||||
Items.SHARK_385 -> 720
|
||||
Items.RAW_SHARK_383 -> 710
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package core.game.ge
|
||||
|
||||
import core.api.StartupListener
|
||||
import org.json.simple.JSONArray
|
||||
import org.json.simple.JSONObject
|
||||
import org.json.simple.parser.JSONParser
|
||||
import core.ServerConstants
|
||||
import java.io.File
|
||||
import java.io.FileReader
|
||||
|
||||
class GEAutoStock : StartupListener {
|
||||
override fun startup() {
|
||||
autostock()
|
||||
}
|
||||
|
||||
companion object {
|
||||
// autostock format should be identical to the botoffers json format.
|
||||
private val DB_PATH = "data" + File.separator + "eco" + File.separator + "autostock.json"
|
||||
|
||||
fun autostock() {
|
||||
if(ServerConstants.GE_AUTOSTOCK_ENABLED) {
|
||||
val parser = JSONParser()
|
||||
val botReader: FileReader? = FileReader(DB_PATH)
|
||||
val botSave = parser.parse(botReader) as JSONObject
|
||||
if (botSave.containsKey("offers")) {
|
||||
val offers = botSave["offers"] as JSONArray
|
||||
for (offer in offers) {
|
||||
val o = offer as JSONObject
|
||||
GrandExchange.addBotOffer(o["item"].toString().toInt(), o["qty"].toString().toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package core.game.ge
|
||||
|
||||
import core.cache.def.impl.ItemDefinition
|
||||
import org.json.simple.JSONArray
|
||||
import org.json.simple.JSONObject
|
||||
import org.json.simple.parser.JSONParser
|
||||
import org.sqlite.SQLiteDataSource
|
||||
import core.ServerConstants
|
||||
import java.io.File
|
||||
import java.io.FileReader
|
||||
import java.sql.Connection
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
/**
|
||||
* Collection of methods for interacting with the grand exchange databases
|
||||
* @author Ceikry
|
||||
*/
|
||||
object GEDB {
|
||||
private var pathString = ""
|
||||
public var connection: Connection? = null
|
||||
private var initialized = false
|
||||
private var connectionRefs = 0
|
||||
private var obtainConnectionLock = ReentrantLock()
|
||||
private var dbRunLock = ReentrantLock()
|
||||
|
||||
fun init() {
|
||||
init(File(ServerConstants.GRAND_EXCHANGE_DATA_PATH + "grandexchange.db").absolutePath)
|
||||
}
|
||||
|
||||
//This needs to be a separate method, so we can call it after the server config has been parsed
|
||||
fun init(path: String)
|
||||
{
|
||||
if(initialized) return
|
||||
pathString = path
|
||||
//Check if the grandexchange.db file already exists. If not, create it and create the tables.
|
||||
if(!File(path).exists())
|
||||
generateAndTransfer()
|
||||
}
|
||||
|
||||
@JvmStatic fun run(closure: (conn: Connection) -> Unit) {
|
||||
dbRunLock.tryLock(10000L, TimeUnit.MILLISECONDS)
|
||||
|
||||
connectionRefs++
|
||||
val con = connect()
|
||||
closure.invoke(con)
|
||||
connectionRefs--
|
||||
|
||||
if(connectionRefs == 0) {
|
||||
con.close()
|
||||
}
|
||||
|
||||
dbRunLock.unlock()
|
||||
}
|
||||
|
||||
private fun connect(): Connection
|
||||
{
|
||||
obtainConnectionLock.tryLock(10000L, TimeUnit.MILLISECONDS)
|
||||
|
||||
if (connection == null || connection!!.isClosed)
|
||||
{
|
||||
val ds = SQLiteDataSource()
|
||||
ds.url = "jdbc:sqlite:$pathString"
|
||||
connection = ds.connection
|
||||
}
|
||||
|
||||
obtainConnectionLock.unlock()
|
||||
return connection!!
|
||||
}
|
||||
|
||||
private fun convertJsonArray(arr: JSONArray): String
|
||||
{
|
||||
val sb = StringBuilder()
|
||||
for ((index, data) in arr.withIndex())
|
||||
{
|
||||
val item = data as JSONObject
|
||||
sb.append("$index")
|
||||
sb.append(",")
|
||||
sb.append(item["id"])
|
||||
sb.append(",")
|
||||
sb.append(item["amount"])
|
||||
if(index + 1 < arr.size)
|
||||
sb.append(":")
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun generateAndTransfer()
|
||||
{
|
||||
val conn = connect() //for sqlite jdbc, attempting to connect to a non-existing .db file creates it.
|
||||
val statement = conn.createStatement()
|
||||
|
||||
//table for tracking player offers - replaces offer_dispatch.json
|
||||
statement.execute(
|
||||
"CREATE TABLE player_offers(" +
|
||||
"uid INTEGER PRIMARY KEY ASC," +
|
||||
"player_uid INTEGER," +
|
||||
"item_id INTEGER," +
|
||||
"amount_total INTEGER," +
|
||||
"amount_complete INTEGER," +
|
||||
"offered_value INTEGER," +
|
||||
"time_stamp INTEGER," +
|
||||
"offer_state INTEGER," +
|
||||
"is_sale INTEGER," +
|
||||
"withdraw_items STRING ," +
|
||||
"slot_index INTEGER," +
|
||||
"total_coin_xc INTEGER)"
|
||||
)
|
||||
|
||||
//table for tracking bot offers - replaces bot_offers.json
|
||||
statement.execute(
|
||||
"CREATE TABLE bot_offers(" +
|
||||
"item_id INTEGER," +
|
||||
"amount INTEGER)"
|
||||
)
|
||||
|
||||
//table for tracking price index data - replaces gedb.xml
|
||||
statement.execute(
|
||||
"CREATE TABLE price_index(" +
|
||||
"item_id INTEGER," +
|
||||
"value INTEGER," +
|
||||
"total_value INTEGER," +
|
||||
"unique_trades INTEGER," +
|
||||
"last_update INTEGER)"
|
||||
)
|
||||
|
||||
//check if the old .json and .xml files exist, and if they do, read them into the sqlite database and remove them.
|
||||
val playerJson = ServerConstants.GRAND_EXCHANGE_DATA_PATH + "offer_dispatch.json"
|
||||
val botJson = ServerConstants.GRAND_EXCHANGE_DATA_PATH + "bot_offers.json"
|
||||
|
||||
if(File(playerJson).exists())
|
||||
{
|
||||
val parser = JSONParser()
|
||||
val reader = FileReader(playerJson)
|
||||
val saveFile = parser.parse(reader) as JSONObject
|
||||
|
||||
if (saveFile.containsKey("offers")) {
|
||||
val offers = saveFile["offers"] as JSONArray
|
||||
|
||||
for (offer in offers) {
|
||||
val o = offer as JSONObject
|
||||
statement.execute("insert into player_offers(player_uid,item_id,amount_total,amount_complete,offered_value,time_stamp,offer_state,is_sale,withdraw_items,total_coin_xc,slot_index) " +
|
||||
"values(" +
|
||||
"${o["playerUID"]}," +
|
||||
"${o["itemId"]}," +
|
||||
"${o["amount"]}," +
|
||||
"${o["completedAmount"]}," +
|
||||
"${o["offeredValue"]}," +
|
||||
"${o["timeStamp"]}," +
|
||||
"${o["offerState"]}," +
|
||||
"${if (o["sale"] as Boolean) 1 else 0}," +
|
||||
"'" + convertJsonArray(o["withdrawItems"] as JSONArray) + "'," +
|
||||
"${o["totalCoinExchange"]}," +
|
||||
"-1)"
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
reader.close()
|
||||
//File(playerJson).delete()
|
||||
}
|
||||
|
||||
if(File(botJson).exists())
|
||||
{
|
||||
val parser = JSONParser()
|
||||
val reader = FileReader(botJson)
|
||||
val saveFile = parser.parse(reader) as JSONObject
|
||||
|
||||
if (saveFile.containsKey("offers")) {
|
||||
val offers = saveFile["offers"] as JSONArray
|
||||
|
||||
for (offer in offers) {
|
||||
val o = offer as JSONObject
|
||||
statement.execute("insert into bot_offers " +
|
||||
"values(${o["item"]},${o["qty"]})")
|
||||
}
|
||||
}
|
||||
reader.close()
|
||||
}
|
||||
statement.close()
|
||||
|
||||
|
||||
//price index isn't worth transferring, so we're just going to make a new one.
|
||||
ItemDefinition.getDefinitions().values.forEach { def ->
|
||||
if(def.isTradeable){
|
||||
PriceIndex.allowItem(def.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package core.game.ge;
|
||||
|
||||
import core.game.component.Component;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.tools.StringUtils;
|
||||
|
||||
/**
|
||||
* Represents the glass used to open the guide prices for the different type of
|
||||
* trade(herbs, logs, runes, etc...)
|
||||
* @author 'Vexia
|
||||
* @date 30/11/2013
|
||||
*/
|
||||
public final class GEGuidePrice {
|
||||
|
||||
/**
|
||||
* Represents the guide price component.
|
||||
*/
|
||||
private static final Component COMPONENT = new Component(642);
|
||||
|
||||
/**
|
||||
* Method used to open a Grand Exchange guide price type.
|
||||
* @param player the player.
|
||||
* @param type the type.
|
||||
*/
|
||||
public static final void open(final Player player, final GuideType type) {
|
||||
player.getInterfaceManager().open(COMPONENT);
|
||||
type.display(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to clear the current items being showed.
|
||||
* @param player the player.
|
||||
*/
|
||||
public static final void clear(final Player player) {
|
||||
for (int i = 135; i < 165; i++) {
|
||||
player.getPacketDispatch().sendInterfaceConfig(COMPONENT.getId(), i, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a guide item shown on the Guide Price interface.
|
||||
* @author 'Vexia
|
||||
* @date 01/12/2013
|
||||
*/
|
||||
public static class GuideItem {
|
||||
|
||||
/**
|
||||
* Represents the item of this type.
|
||||
*/
|
||||
private final int item;
|
||||
|
||||
/**
|
||||
* Represents the unlock child.
|
||||
*/
|
||||
private final int[] childData;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code GEGuidePrice} {@code Object}.
|
||||
* @param item the item.
|
||||
* @param childData the child data.
|
||||
*/
|
||||
public GuideItem(final int item, final int... childData) {
|
||||
this.item = item;
|
||||
this.childData = childData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item.
|
||||
* @return The item.
|
||||
*/
|
||||
public int getItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the childData.
|
||||
* @return The childData.
|
||||
*/
|
||||
public int[] getChildData() {
|
||||
return childData;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a guide price type of trade(herbs, logs, etc...)
|
||||
* @author 'Vexia
|
||||
* @date 30/11/2013
|
||||
*/
|
||||
public enum GuideType {
|
||||
LOGS(new int[] { 0, 0 }, new GuideItem(1511, 155), new GuideItem(2862, 156), new GuideItem(1521, 157), new GuideItem(1519, 158), new GuideItem(6333, 159), new GuideItem(1517, 160), new GuideItem(6332, 161), new GuideItem(12581, 162), new GuideItem(1515, 163), new GuideItem(1513, 164)), ORES(new int[] { 40, 44 }, new GuideItem(436, 33), new GuideItem(438, 34), new GuideItem(440, 35), new GuideItem(442, 36), new GuideItem(453, 37), new GuideItem(444, 38), new GuideItem(447, 39), new GuideItem(449, 40), new GuideItem(451, 41)), RUNES(new int[] { 215, 216 }, new GuideItem(1436, 183), new GuideItem(7936, 184), new GuideItem(556, 185), new GuideItem(558, 186), new GuideItem(555, 187), new GuideItem(557, 188), new GuideItem(554, 189), new GuideItem(559, 190), new GuideItem(564, 191), new GuideItem(562, 192), new GuideItem(9075, 193), new GuideItem(561, 194), new GuideItem(563, 195), new GuideItem(560, 196), new GuideItem(565, 197), new GuideItem(566, 198)), HERBS(new int[] { 130, 135 }, new GuideItem(249, 119), new GuideItem(251, 120), new GuideItem(253, 121), new GuideItem(255, 122), new GuideItem(257, 123), new GuideItem(2998, 124), new GuideItem(259, 125), new GuideItem(12172, 126), new GuideItem(261, 127), new GuideItem(263, 128), new GuideItem(3000, 129), new GuideItem(265, 130), new GuideItem(2481, 131), new GuideItem(267, 132), new GuideItem(269, 133)), WEAPONS_AND_ARMOUR(new int[] { 88, 89 }, new GuideItem(11834, 73), new GuideItem(11838, 74), new GuideItem(11842, 75), new GuideItem(11864, 76), new GuideItem(11870, 77), new GuideItem(11846, 78), new GuideItem(11848, 79), new GuideItem(11850, 80), new GuideItem(11856, 81), new GuideItem(11732, 82), new GuideItem(4151, 83), new GuideItem(11235, 84), new GuideItem(6739, 85), new GuideItem(4587, 86), new GuideItem(4153, 87));
|
||||
|
||||
/**
|
||||
* Constructs a new {@code GEGuidePrice} {@code Object}.
|
||||
* @param childData the childData.
|
||||
* @param items the guide items.
|
||||
*/
|
||||
GuideType(final int[] childData, final GuideItem... items) {
|
||||
this.childData = childData;
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the guide items.
|
||||
*/
|
||||
private final GuideItem[] items;
|
||||
|
||||
/**
|
||||
* Represents the child data for the guide type.
|
||||
*/
|
||||
private final int childData[];
|
||||
|
||||
/**
|
||||
* Gets the items.
|
||||
* @return The items.
|
||||
*/
|
||||
public GuideItem[] getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the childData.
|
||||
* @return The childData.
|
||||
*/
|
||||
public int[] getChildData() {
|
||||
return childData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to display the guide type.
|
||||
* @param player the player.
|
||||
*/
|
||||
public void display(final Player player) {
|
||||
player.setAttribute("guide-price", this);
|
||||
if (this != LOGS) {
|
||||
clear(player);
|
||||
}
|
||||
player.getPacketDispatch().sendString("Guide Prices: " + StringUtils.formatDisplayName(name()), COMPONENT.getId(), 14);
|
||||
for (int i = getChildData()[0]; i < getChildData()[1]; i++) {
|
||||
player.getPacketDispatch().sendInterfaceConfig(642, i, false);
|
||||
}
|
||||
for (GuideItem item : getItems()) {
|
||||
player.getPacketDispatch().sendString("" + GrandExchange.getRecommendedPrice(item.item, false) + " gp", COMPONENT.getId(), item.getChildData()[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package core.game.ge;
|
||||
|
||||
import core.game.node.item.Item;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Holds the Grand Exchange item sets.
|
||||
* @author Emperor
|
||||
*/
|
||||
public enum GEItemSet {
|
||||
|
||||
BRONZE_L(11814, 1155, 1117, 1075, 1189), BRONZE_SK(11816, 1155, 1117, 1087, 1189), IRON_L(11818, 1153, 1115, 1067, 1191), IRON_SK(11820, 1153, 1115, 1081, 1191), STEEL_L(11822, 1157, 1119, 1069, 1193), STEEL_SK(11824, 1157, 1119, 1083, 1193), BLACK_L(11826, 1165, 1125, 1077, 1195), BLACK_SK(11828, 1165, 1125, 1089, 1195), MITHRIL_L(11830, 1159, 1121, 1071, 1197), MITHRIL_SK(11832, 1159, 1121, 1085, 1197), ADAMANT_L(11834, 1161, 1123, 1073, 1199), ADAMANT_SK(11836, 1161, 1123, 1091, 1199), RUNE_L(11838, 1163, 1127, 1079, 1201), RUNE_SK(11840, 1163, 1127, 1093, 1201), DRAGON_L(11842, 1149, 3140, 4087), DRAGON_SK(11844, 1149, 3140, 4585),
|
||||
// NULL_1(-1), //This would create the spaces between the sets (uncomment
|
||||
// when needed)
|
||||
AHRIMS(11846, 4708, 4712, 4714, 4710), DHAROKS(11848, 4716, 4720, 4722, 4718), GUTHANS(11850, 4724, 4728, 4730, 4726), KARILS(11852, 4732, 4736, 4738, 4734), TORAGS(11854, 4745, 4749, 4751, 4747), VERACS(11856, 4753, 4757, 4759, 4755), THIRD_AGE_MELEE(11858, 10350, 10348, 10346, 10352), THIRD_AGE_RANGE(11860, 10334, 10330, 10332, 10336), THIRD_AGE_MAGE(11862, 10342, 10338, 10340, 10344), GREEN_DHIDE(11864, 1135, 1099, 1065), BLUE_DHIDE(11866, 2499, 2493, 2487), RED_DHIDE(11868, 2501, 2495, 2489), BLACK_DHIDE(11870, 2503, 2497, 2491), MYSTIC(11872, 4089, 4091, 4093, 4095, 4097), LIGHT_MYSTIC(11960, 4109, 4111, 4113, 4115, 4117), DARK_MYSTIC(11962, 4099, 4101, 4103, 4105, 4107), INFINITY(11874, 6918, 6916, 6924, 6922, 6920), SPLITBARK(11876, 3385, 3387, 3389, 3391, 3393), BLACK_TRIMMED_L(11878, 2587, 2583, 2585, 2589), BLACK_TRIMMED_SK(11880, 2587, 2583, 3472, 2589), BLACK_GOLD_TRIMMED_L(11882, 2595, 2591, 2593, 2597), BLACK_GOLD_TRIMMED_SK(11884, 2595, 2591, 3473, 2597), ADAMANT_TRIMMED_L(11886, 2605, 2599, 2601, 2603), ADAMANT_TRIMMED_SK(11888, 2605, 2599, 3474), ADAMANT_GOLD_TRIMMED_L(11890, 2613, 2607, 2609, 2611), ADAMANT_GOLD_TRIMMED_SK(11892, 2613, 3475, 2611), RUNE_TRIMMED_L(11894, 2627, 2623, 2625, 2629), RUNE_TRIMMED_SK(11896, 2627, 2623, 3477, 2629), RUNE_GOLD_TRIMMED_L(11898, 2619, 2615, 2617, 2621), RUNE_GOLD_TRIMMED_SK(11900, 2619, 2615, 3476, 2621), ENCHANTED(11902, 7400, 7399, 7398), TRIMMED_BLUE_WIZARD(11904, 7396, 7392, 7388), GOLD_TRIMMED_BLUE_WIZARD(11906, 7394, 7390, 7386), TRIMMED_LEATHER(11908, 7364, 7368), GOLD_TRIMMED_LEATHER(11910, 7362, 7366), GREEN_DHIDE_T(11912, 7372, 7380), GREEN_DHIDE_G(11914, 7370, 7378), BLUE_DHIDE_T(11916, 7376, 7384), BLUE_DHIDE_G(11918, 7374, 7382), GREEN_DHIDE_BLESSED(11920, 10382, 10378, 10380, 10376), BLUE_DHIDE_BLESSED(11922, 10390, 10386, 10388, 10384), RED_DHIDE_BLESSED(11924, 10374, 10370, 10372, 10368), GUTHIX_L(11926, 2673, 2669, 2671, 2675), SARADOMIN_L(11928, 2665, 2661, 2663, 2667), ZAMORAK_L(11930, 2657, 2653, 2655, 2659), GUTHIX_SK(11932, 2673, 2669, 3480, 2675), SARADOMIN_SK(11934, 2665, 2661, 3479, 2667), ZAMORAK_SK(11936, 2657, 2653, 3478, 2659), GILDED_L(11938, 3486, 3481, 3483, 3488), GILDED_SK(11940, 3486, 3481, 3485, 3488), ROCKSHELL(11942, 6128, 6129, 6130, 6151, 6145), SPINED(11944, 6131, 6133, 6135, 6149, 6143), SKELETAL(11946, 6137, 6139, 6141, 6153, 6147), CANNON(11967, 6, 8, 10, 12);
|
||||
|
||||
/**
|
||||
* The item sets mapping.
|
||||
*/
|
||||
private static final Map<Integer, GEItemSet> ITEM_SETS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The items array.
|
||||
*/
|
||||
private static Item[] itemArray;
|
||||
|
||||
/**
|
||||
* Populate the mapping.
|
||||
*/
|
||||
static {
|
||||
itemArray = new Item[values().length];
|
||||
for (int i = 0; i < itemArray.length; i++) {
|
||||
GEItemSet set = values()[i];
|
||||
itemArray[i] = set.itemId == -1 ? new Item() : new Item(set.itemId);
|
||||
ITEM_SETS.put(set.itemId, set);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item set for the given id.
|
||||
* @param setId The set item id.
|
||||
* @return The item set object.
|
||||
*/
|
||||
public static GEItemSet forId(int setId) {
|
||||
return ITEM_SETS.get(setId);
|
||||
}
|
||||
|
||||
/**
|
||||
* The item id.
|
||||
*/
|
||||
private int itemId;
|
||||
|
||||
/**
|
||||
* The components.
|
||||
*/
|
||||
private int[] components;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code GEItemSet} {@code Object}.
|
||||
* @param itemId The item id.
|
||||
* @param components The components.
|
||||
*/
|
||||
private GEItemSet(int itemId, int... components) {
|
||||
this.itemId = itemId;
|
||||
this.components = components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the itemId.
|
||||
* @return The itemId.
|
||||
*/
|
||||
public int getItemId() {
|
||||
return itemId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the components.
|
||||
* @return The components.
|
||||
*/
|
||||
public int[] getComponents() {
|
||||
return Arrays.copyOf(components, components.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item array.
|
||||
* @return The item array.
|
||||
*/
|
||||
public static Item[] getItemArray() {
|
||||
return Arrays.copyOf(itemArray, itemArray.length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
package core.game.ge
|
||||
|
||||
import core.api.*
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.entity.player.info.PlayerDetails
|
||||
import core.game.node.entity.player.link.audio.Audio
|
||||
import core.game.system.task.Pulse
|
||||
import core.ServerConstants
|
||||
import core.tools.SystemLogger
|
||||
import core.game.system.command.Privilege
|
||||
import core.game.system.config.ItemConfigParser
|
||||
import core.game.world.GameWorld
|
||||
import core.game.world.repository.Repository
|
||||
import core.tools.colorize
|
||||
import java.lang.Integer.max
|
||||
import java.util.concurrent.LinkedBlockingDeque
|
||||
|
||||
/**
|
||||
* Handles the exchanging of offers, offer update thread, etc.
|
||||
* @author Ceikry
|
||||
*/
|
||||
class GrandExchange : StartupListener, Commands {
|
||||
|
||||
/**
|
||||
* Fallback safety check to make sure we don't start the GE twice under any circumstance
|
||||
*/
|
||||
var isRunning = false
|
||||
|
||||
/**
|
||||
* Initializes the offer manager and spawns an update thread.
|
||||
* @param local whether or not the GE should be the local in-code server rather than some hypothetical remote implementation.
|
||||
*/
|
||||
fun boot(){
|
||||
if(isRunning) return
|
||||
|
||||
SystemLogger.logGE("Initializing GE Update Worker")
|
||||
|
||||
Thread {
|
||||
Thread.currentThread().name = "GE Update Worker"
|
||||
while(true) {
|
||||
var offer = pendingOffers.takeFirst()
|
||||
offer.writeNew()
|
||||
offer = getOfferByUid(offer.uid) ?: continue
|
||||
selectPotentialMatches(offer).asSequence()
|
||||
.sortedBy { if (offer.sell) -it.offeredValue else it.offeredValue }
|
||||
.filter { if (offer.sell) it.offeredValue >= offer.offeredValue else it.offeredValue <= offer.offeredValue }
|
||||
.forEach { match -> exchange(offer, match) }
|
||||
if (!offer.isBot && offer.amountLeft > 0 && !offer.sell)
|
||||
tryExchangeWithBots(offer)
|
||||
}
|
||||
}.start()
|
||||
|
||||
isRunning = true
|
||||
}
|
||||
|
||||
|
||||
private fun tryExchangeWithBots(offer: GrandExchangeOffer) {
|
||||
GEDB.run { conn ->
|
||||
val query = conn.prepareStatement(GET_MATCH_FROM_BOT_OFFERS)
|
||||
query.setInt(1, offer.itemID)
|
||||
val res = query.executeQuery()
|
||||
|
||||
if (res.next()) {
|
||||
exchange(offer, GrandExchangeOffer.fromBotQuery(res).also { it.timeStamp = offer.timeStamp - 1L })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectPotentialMatches(offer: GrandExchangeOffer): List<GrandExchangeOffer> {
|
||||
val matches = ArrayList<GrandExchangeOffer>()
|
||||
GEDB.run { conn ->
|
||||
val query = conn.prepareStatement(GET_MATCHES_FROM_PLAYER_OFFERS)
|
||||
query.setInt(1, offer.itemID)
|
||||
query.setBoolean(2, !offer.sell)
|
||||
val res = query.executeQuery()
|
||||
while (res.next()) {
|
||||
matches.add(GrandExchangeOffer.fromQuery(res))
|
||||
}
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
override fun defineCommands() {
|
||||
define("addbotoffer", Privilege.ADMIN) {player, strings ->
|
||||
val id = strings[1].toInt()
|
||||
val amount = strings[2].toInt()
|
||||
addBotOffer(id, amount)
|
||||
notify(player, "Added ${amount}x ${getItemName(id)} to the bot offers.")
|
||||
}
|
||||
|
||||
define("bange", Privilege.ADMIN) {player, strings ->
|
||||
val id = strings[1].toInt()
|
||||
PriceIndex.banItem(id)
|
||||
notify(player, "Banned ${getItemName(id)} from GE trade.")
|
||||
}
|
||||
|
||||
define("allowge", Privilege.ADMIN) {player, strings ->
|
||||
val id = strings[1].toInt()
|
||||
PriceIndex.allowItem(id)
|
||||
notify(player, "Allowed ${getItemName(id)} for GE trade.")
|
||||
}
|
||||
|
||||
define("geprivacy", Privilege.STANDARD) {player, _ ->
|
||||
val current = getAttribute(player, "ge-exclude", false)
|
||||
val new = !current
|
||||
notify(player, "Your name is now ${if (new) colorize("%RHIDDEN") else colorize("%RSHOWN")}.")
|
||||
setAttribute(player, "/save:ge-exclude", new)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val pendingOffers = LinkedBlockingDeque<GrandExchangeOffer>()
|
||||
private val GET_SPECIFIC_OFFER_BY_UID = "SELECT * FROM player_offers WHERE uid = ?;"
|
||||
private val GET_MATCHES_FROM_PLAYER_OFFERS = "SELECT * FROM player_offers WHERE item_id = ? AND is_sale = ? AND offer_state < 4 AND NOT offer_state = 2;"
|
||||
private val GET_MATCH_FROM_BOT_OFFERS = "SELECT * FROM bot_offers WHERE item_id = ?;"
|
||||
|
||||
private fun getOfferByUid(uid: Long): GrandExchangeOffer? {
|
||||
var offer: GrandExchangeOffer? = null
|
||||
GEDB.run { conn ->
|
||||
val query = conn.prepareStatement(GET_SPECIFIC_OFFER_BY_UID)
|
||||
query.setLong(1, uid)
|
||||
val res = query.executeQuery()
|
||||
|
||||
if (res.next())
|
||||
offer = GrandExchangeOffer.fromQuery(res)
|
||||
}
|
||||
return offer
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getRecommendedPrice(itemID: Int, from_bot: Boolean = false): Int {
|
||||
var base = max(PriceIndex.getValue(itemID), getItemDefPrice(itemID))
|
||||
if (from_bot) base = (max(BotPrices.getPrice(itemID), base) * 1.10).toInt()
|
||||
return base
|
||||
}
|
||||
|
||||
private fun getItemDefPrice(itemID: Int): Int {
|
||||
return max(itemDefinition(itemID).getConfiguration(ItemConfigParser.GE_PRICE) ?: 0, itemDefinition(itemID).value)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getOfferStats(itemID: Int, sale: Boolean) : String
|
||||
{
|
||||
val sb = StringBuilder()
|
||||
|
||||
GEDB.run { conn ->
|
||||
var foundOffers = 0
|
||||
var totalAmount = 0
|
||||
var bestPrice = 0
|
||||
var stmt = conn.createStatement()
|
||||
|
||||
if (!sale) {
|
||||
var botAmt = 0
|
||||
var botPrice = 0
|
||||
val player_offers = stmt.executeQuery("SELECT * from player_offers where item_id = $itemID AND is_sale = 1 AND offer_state < 4 AND NOT offer_state = 2")
|
||||
|
||||
while (player_offers.next()) {
|
||||
val o = GrandExchangeOffer.fromQuery(player_offers)
|
||||
++foundOffers
|
||||
totalAmount += o.amountLeft
|
||||
if (o.offeredValue < bestPrice || bestPrice == 0)
|
||||
bestPrice = o.offeredValue
|
||||
}
|
||||
|
||||
stmt.close()
|
||||
stmt = conn.createStatement()
|
||||
val bot_offers = stmt.executeQuery("SELECT * from bot_offers where item_id = $itemID")
|
||||
if (bot_offers.next()) {
|
||||
val o = GrandExchangeOffer.fromBotQuery(bot_offers)
|
||||
botAmt = o.amount
|
||||
botPrice = getRecommendedPrice(itemID, true)
|
||||
}
|
||||
|
||||
sb.append("Player Stock: <col=FFFFFF>$totalAmount ")
|
||||
sb.append("</col> Lowest Price: <col=FFFFFF>$bestPrice<br>")
|
||||
sb.append("-".repeat(50))
|
||||
sb.append("</col><br>Bot Stock: <col=FFFFFF>$botAmt ")
|
||||
sb.append("</col> Bot Price: <col=FFFFFF>$botPrice")
|
||||
} else {
|
||||
val buy_offers = stmt.executeQuery("SELECT * from player_offers where item_id = $itemID AND is_sale = 0 AND offer_state < 4 AND NOT offer_state = 2")
|
||||
|
||||
while (buy_offers.next()) {
|
||||
val o = GrandExchangeOffer.fromQuery(buy_offers)
|
||||
++foundOffers
|
||||
totalAmount += o.amountLeft
|
||||
if (o.offeredValue > bestPrice)
|
||||
bestPrice = o.offeredValue
|
||||
}
|
||||
|
||||
sb.append("Buy Offers: <col=FFFFFF>$totalAmount ")
|
||||
sb.append("</col>Highest Offer: <col=FFFFFF>$bestPrice</col>")
|
||||
}
|
||||
|
||||
stmt.close()
|
||||
}
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
fun addBotOffer(itemID: Int, amount: Int): Boolean
|
||||
{
|
||||
if (!PriceIndex.canTrade(itemID))
|
||||
return false
|
||||
|
||||
val offer = GrandExchangeOffer.createBotOffer(itemID, amount)
|
||||
pendingOffers.addLast(offer)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun dispatch(player: Player, offer: GrandExchangeOffer) : Boolean
|
||||
{
|
||||
if ( offer.amount < 1 )
|
||||
sendMessage(player, "You must choose the quantity you wish to buy!").also { return false }
|
||||
|
||||
if ( offer.offeredValue < 1 )
|
||||
sendMessage(player, "You must choose the price you wish to buy for!").also { return false }
|
||||
|
||||
if ( offer.offerState != OfferState.PENDING || offer.uid != 0L )
|
||||
{
|
||||
SystemLogger.logWarn(this::class.java, "[GE] DISPATCH FAILURE: ${offer.offerState.name}, UID: ${offer.uid}")
|
||||
return false
|
||||
}
|
||||
|
||||
if ( player.isArtificial )
|
||||
offer.playerUID = PlayerDetails.getDetails("2009scape").uid.also { offer.isBot = true }
|
||||
else
|
||||
offer.playerUID = player.details.uid
|
||||
|
||||
offer.offerState = OfferState.REGISTERED
|
||||
//GrandExchangeRecords.getInstance(player).update(offer)
|
||||
|
||||
if (offer.sell && !player.isArtificial) {
|
||||
val username = if (getAttribute(player, "ge-exclude", false)) "?????" else player.username
|
||||
Repository.sendNews(username + " just offered " + offer.amount + " " + getItemName(offer.itemID) + " on the GE.")
|
||||
}
|
||||
|
||||
if (ServerConstants.I_AM_A_CHEATER) {
|
||||
val otherO = GrandExchangeOffer()
|
||||
otherO.itemID = offer.itemID
|
||||
otherO.amount = offer.amount
|
||||
otherO.sell = !offer.sell
|
||||
otherO.offeredValue = offer.offeredValue
|
||||
offer.writeNew()
|
||||
GameWorld.Pulser.submit(object : Pulse(5) {
|
||||
override fun pulse(): Boolean {
|
||||
val offer2 = getOfferByUid(offer.uid) ?: return false
|
||||
exchange(offer2, otherO)
|
||||
return true
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
pendingOffers.add(offer)
|
||||
return true
|
||||
}
|
||||
|
||||
fun exchange(offer: GrandExchangeOffer, other: GrandExchangeOffer)
|
||||
{
|
||||
if(offer.sell == other.sell) return //Don't exchange if they are both buy/sell offers
|
||||
val amount = Integer.min(offer.amount - offer.completedAmount, other.amount - other.completedAmount)
|
||||
|
||||
if (amount == 0) return
|
||||
|
||||
val seller = if(offer.sell) offer else other
|
||||
val buyer = if(offer == seller) other else offer
|
||||
|
||||
val sellerBias = seller.timeStamp > buyer.timeStamp
|
||||
|
||||
//If the buyer is buying for less than the seller is selling for, don't exchange
|
||||
if(seller.offeredValue > buyer.offeredValue) return
|
||||
|
||||
seller.completedAmount += amount
|
||||
buyer.completedAmount += amount
|
||||
|
||||
if(seller.amountLeft < 1 && seller.player != null)
|
||||
seller.player!!.audioManager.send(Audio(4042,1,1))
|
||||
|
||||
seller.addWithdrawItem(995, amount * if(sellerBias) buyer.offeredValue else seller.offeredValue)
|
||||
buyer.addWithdrawItem(seller.itemID, amount)
|
||||
|
||||
if(!sellerBias)
|
||||
buyer.addWithdrawItem(995, amount * (buyer.offeredValue - seller.offeredValue))
|
||||
|
||||
if(seller.amountLeft < 1)
|
||||
seller.offerState = OfferState.COMPLETED
|
||||
if(buyer.amountLeft < 1)
|
||||
buyer.offerState = OfferState.COMPLETED
|
||||
|
||||
val totalCoinXC = (if(sellerBias) buyer.offeredValue else seller.offeredValue) * amount
|
||||
|
||||
seller.totalCoinExchange += totalCoinXC
|
||||
buyer.totalCoinExchange += totalCoinXC
|
||||
|
||||
if(canUpdatePriceIndex(seller, buyer))
|
||||
PriceIndex.addTrade(offer.itemID, amount, (totalCoinXC / amount))
|
||||
|
||||
/*
|
||||
if (seller.amountLeft > 0) {
|
||||
Discord.postOfferUpdate(true, seller.itemID, seller.offeredValue, seller.amountLeft)
|
||||
}
|
||||
|
||||
if (buyer.amountLeft > 0) {
|
||||
Discord.postOfferUpdate(false, buyer.itemID, buyer.offeredValue, buyer.amountLeft)
|
||||
}
|
||||
*/
|
||||
|
||||
seller.update()
|
||||
val sellerPlayer = Repository.uid_map[seller.playerUID]
|
||||
sellerPlayer?.let { GrandExchangeRecords.getInstance(sellerPlayer).visualizeRecords() }
|
||||
buyer.update()
|
||||
val buyerPlayer = Repository.uid_map[buyer.playerUID]
|
||||
buyerPlayer?.let { GrandExchangeRecords.getInstance(buyerPlayer).visualizeRecords() }
|
||||
}
|
||||
|
||||
private fun canUpdatePriceIndex(seller: GrandExchangeOffer, buyer: GrandExchangeOffer): Boolean {
|
||||
if(seller.playerUID == buyer.playerUID) return false
|
||||
if(!ServerConstants.BOTS_INFLUENCE_PRICE_INDEX && (seller.isBot || buyer.isBot)) return false;
|
||||
return true
|
||||
}
|
||||
|
||||
fun getValidOffers(): List<GrandExchangeOffer>
|
||||
{
|
||||
val offers = ArrayList<GrandExchangeOffer>()
|
||||
|
||||
GEDB.run { conn ->
|
||||
val stmt = conn.createStatement()
|
||||
|
||||
val results =
|
||||
stmt.executeQuery("SELECT * FROM player_offers WHERE offer_state < 4 AND NOT offer_state = 2")
|
||||
while (results.next()) {
|
||||
val o = GrandExchangeOffer.fromQuery(results)
|
||||
offers.add(o)
|
||||
}
|
||||
stmt.close()
|
||||
}
|
||||
return offers
|
||||
}
|
||||
|
||||
fun getBotOffers(): List<GrandExchangeOffer>
|
||||
{
|
||||
val offers = ArrayList<GrandExchangeOffer>()
|
||||
|
||||
GEDB.run { conn ->
|
||||
val stmt = conn.createStatement()
|
||||
|
||||
val results = stmt.executeQuery("SELECT item_id,amount FROM bot_offers WHERE amount > 0")
|
||||
while (results.next()) {
|
||||
val o = GrandExchangeOffer.fromBotQuery(results)
|
||||
offers.add(o)
|
||||
}
|
||||
stmt.close()
|
||||
}
|
||||
return offers
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun startup(){
|
||||
GEDB.init()
|
||||
boot()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package core.game.ge
|
||||
|
||||
import core.api.getAttribute
|
||||
import core.cache.def.impl.ItemDefinition
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.item.Item
|
||||
import core.net.packet.PacketRepository
|
||||
import core.net.packet.context.ContainerContext
|
||||
import core.net.packet.context.GrandExchangeContext
|
||||
import core.net.packet.out.ContainerPacket
|
||||
import core.net.packet.out.GrandExchangePacket
|
||||
import core.integrations.discord.Discord
|
||||
import core.game.world.repository.Repository
|
||||
import java.sql.ResultSet
|
||||
|
||||
|
||||
/**
|
||||
* A struct holding all the data for grand exchange offers.
|
||||
*
|
||||
* @author Ceikry
|
||||
*/
|
||||
|
||||
class GrandExchangeOffer() {
|
||||
var itemID = 0
|
||||
var amount = 0
|
||||
var completedAmount = 0
|
||||
var offeredValue = 0
|
||||
var index = 0
|
||||
var sell = false
|
||||
var offerState = OfferState.PENDING
|
||||
var uid: Long = 0
|
||||
var timeStamp: Long = 0
|
||||
var withdraw = arrayOfNulls<Item>(2)
|
||||
var totalCoinExchange = 0
|
||||
var player: Player? = null
|
||||
var playerUID = 0
|
||||
var isLimitation = false
|
||||
var isBot = false
|
||||
|
||||
/**
|
||||
* Gets the total amount of money entered.
|
||||
* @return The total value.
|
||||
*/
|
||||
val totalValue: Int
|
||||
get() = offeredValue * amount
|
||||
|
||||
/**
|
||||
* Gets the amount of this item left to buy.
|
||||
* @return The amount.
|
||||
*/
|
||||
val amountLeft: Int
|
||||
get() = amount - completedAmount
|
||||
|
||||
/**
|
||||
* Checks if this offer is still active for dispatching.
|
||||
* @return `True` if so.
|
||||
*/
|
||||
val isActive: Boolean
|
||||
get() = offerState != OfferState.ABORTED && offerState != OfferState.PENDING && offerState != OfferState.COMPLETED && offerState != OfferState.REMOVED
|
||||
|
||||
fun addWithdrawItem(id: Int, amount: Int)
|
||||
{
|
||||
if(amount == 0) return
|
||||
//loop checking if the item is already present first
|
||||
for(item in withdraw)
|
||||
if(item != null && item.id == id)
|
||||
{
|
||||
item.amount += amount
|
||||
return
|
||||
}
|
||||
|
||||
//if we make it to this point, the item was not present. Loop to find first null slot and stick item there.
|
||||
for((index,item) in withdraw.withIndex())
|
||||
if(item == null)
|
||||
{
|
||||
withdraw[index] = Item(id, amount)
|
||||
return
|
||||
}
|
||||
|
||||
//send container update packet to player if they exist (are online)
|
||||
if ( player != null )
|
||||
visualize(player)
|
||||
}
|
||||
|
||||
fun visualize(player: Player?)
|
||||
{
|
||||
player ?: return
|
||||
PacketRepository.send(
|
||||
GrandExchangePacket::class.java,
|
||||
GrandExchangeContext(player, index.toByte(), offerState.ordinal.toByte(), itemID.toShort(),
|
||||
sell, offeredValue, amount, completedAmount, totalCoinExchange)
|
||||
)
|
||||
PacketRepository.send(ContainerPacket::class.java, ContainerContext(player, -1, -1757, 523 + index, withdraw, false))
|
||||
}
|
||||
|
||||
fun update()
|
||||
{
|
||||
GEDB.run { conn ->
|
||||
if (isBot) {
|
||||
val stmt = conn.prepareStatement("UPDATE bot_offers SET amount = ? WHERE item_id = ?")
|
||||
stmt.setInt(1, amountLeft)
|
||||
stmt.setInt(2, itemID)
|
||||
stmt.executeUpdate()
|
||||
stmt.close()
|
||||
} else {
|
||||
val stmt = conn.prepareStatement("UPDATE player_offers SET amount_complete = ?, offer_state = ?, total_coin_xc = ?, withdraw_items = ?, slot_index = ? WHERE uid = ?")
|
||||
stmt.setInt(1, completedAmount)
|
||||
stmt.setInt(2, offerState.ordinal)
|
||||
stmt.setInt(3, totalCoinExchange)
|
||||
stmt.setString(4, encodeWithdraw())
|
||||
stmt.setInt(5, index)
|
||||
stmt.setLong(6, uid)
|
||||
stmt.executeUpdate()
|
||||
stmt.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when writing a brand new offer to the database. Should not be used under any other circumstance **/
|
||||
fun writeNew()
|
||||
{
|
||||
GEDB.run { conn ->
|
||||
if (isBot) {
|
||||
val stmt = conn.createStatement()
|
||||
val result = stmt.executeQuery("SELECT * from bot_offers where item_id = $itemID")
|
||||
val isExists = result.next()
|
||||
|
||||
if (isExists) {
|
||||
val oldAmount = result.getInt("amount")
|
||||
stmt.executeUpdate("UPDATE bot_offers set amount = ${oldAmount + amount} where item_id = $itemID")
|
||||
} else
|
||||
stmt.executeUpdate("INSERT INTO bot_offers(item_id,amount) values($itemID,$amount)")
|
||||
stmt.close()
|
||||
} else {
|
||||
val stmt = conn.createStatement()
|
||||
stmt.executeUpdate(
|
||||
"INSERT INTO player_offers(player_uid, item_id, amount_total, offered_value, time_stamp, offer_state, is_sale, slot_index) " +
|
||||
"values($playerUID,$itemID,$amount,$offeredValue,${System.currentTimeMillis()},${offerState.ordinal},${if (sell) 1 else 0}, $index)"
|
||||
)
|
||||
val nowuid = stmt.executeQuery("SELECT last_insert_rowid()")
|
||||
uid = nowuid.getLong(1)
|
||||
GrandExchangeRecords.getInstance(player).offerRecords[index] = GrandExchangeRecords.OfferRecord(uid, index)
|
||||
visualize(player)
|
||||
stmt.close()
|
||||
|
||||
val username = if (getAttribute(player ?: return@run, "ge-exclude", false)) "?????"
|
||||
else player?.username ?: "?????"
|
||||
|
||||
Discord.postNewOffer(sell, itemID, offeredValue, amount, username)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun encodeWithdraw() : String
|
||||
{
|
||||
val sb = StringBuilder()
|
||||
for((index, item) in withdraw.withIndex())
|
||||
{
|
||||
sb.append(index)
|
||||
sb.append(",")
|
||||
if(item == null)
|
||||
sb.append("null")
|
||||
else
|
||||
sb.append(item.id)
|
||||
sb.append(",")
|
||||
if(item == null)
|
||||
sb.append("null")
|
||||
else
|
||||
sb.append(item.amount)
|
||||
|
||||
if(index + 1 < withdraw.size)
|
||||
sb.append(":")
|
||||
}
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "[name=" + ItemDefinition.forId(itemID).name + ", itemId=" + itemID + ", amount=" + amount + ", completedAmount=" + completedAmount + ", offeredValue=" + offeredValue + ", index=" + index + ", sell=" + sell + ", state=" + offerState + ", withdraw=" + withdraw.contentToString() + ", totalCoinExchange=" + totalCoinExchange + ", playerUID=" + playerUID + "]"
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromQuery(result: ResultSet): GrandExchangeOffer
|
||||
{
|
||||
val o = GrandExchangeOffer()
|
||||
o.itemID = result.getInt("item_id")
|
||||
o.amount = result.getInt("amount_total")
|
||||
o.completedAmount = result.getInt("amount_complete")
|
||||
o.offeredValue = result.getInt("offered_value")
|
||||
o.sell = result.getInt("is_sale") == 1
|
||||
o.offerState = OfferState.values()[result.getInt("offer_state")]
|
||||
o.uid = result.getLong("uid")
|
||||
o.timeStamp = result.getLong("time_stamp")
|
||||
|
||||
val itemString = result.getString("withdraw_items")
|
||||
if(itemString != null && itemString.isNotEmpty()) {
|
||||
val items = itemString.split(":")
|
||||
for (item in items) {
|
||||
if(item.isEmpty()) continue
|
||||
val tokens = item.split(",")
|
||||
val index = tokens[0].toInt()
|
||||
if (tokens[1] == "null") continue //Skip null slots
|
||||
o.withdraw[index] = Item(tokens[1].toInt(), tokens[2].toInt())
|
||||
}
|
||||
}
|
||||
|
||||
o.totalCoinExchange = result.getInt("total_coin_xc")
|
||||
o.playerUID = result.getInt("player_uid")
|
||||
o.index = result.getInt("slot_index")
|
||||
o.player = Repository.players.firstOrNull { it.details.accountInfo.uid == o.playerUID }
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
fun fromBotQuery(result: ResultSet): GrandExchangeOffer
|
||||
{
|
||||
val o = GrandExchangeOffer()
|
||||
o.sell = true
|
||||
o.amount = result.getInt("amount")
|
||||
o.offerState = OfferState.REGISTERED
|
||||
o.itemID = result.getInt("item_id")
|
||||
o.offeredValue = GrandExchange.getRecommendedPrice(o.itemID, true)
|
||||
o.isBot = true
|
||||
o.timeStamp = System.currentTimeMillis()
|
||||
return o
|
||||
}
|
||||
|
||||
fun createBotOffer(itemId: Int, amount: Int, sale: Boolean = true) : GrandExchangeOffer
|
||||
{
|
||||
val o = GrandExchangeOffer()
|
||||
o.sell = sale
|
||||
o.itemID = itemId
|
||||
o.amount = amount
|
||||
o.offeredValue = GrandExchange.getRecommendedPrice(itemId, true)
|
||||
o.offerState = OfferState.REGISTERED
|
||||
o.isBot = true
|
||||
return o
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
package core.game.ge
|
||||
|
||||
import core.api.*
|
||||
import core.cache.def.impl.ItemDefinition
|
||||
import core.game.component.Component
|
||||
import core.game.node.entity.player.Player
|
||||
import org.json.simple.JSONArray
|
||||
import org.json.simple.JSONObject
|
||||
import org.rs09.consts.Components
|
||||
import core.tools.SystemLogger
|
||||
import java.text.NumberFormat
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* Handles the grand exchange interfaces for the player.
|
||||
*
|
||||
* @author Emperor
|
||||
* @author Angle
|
||||
*/
|
||||
|
||||
class GrandExchangeRecords(private val player: Player? = null) : PersistPlayer, LoginListener {
|
||||
var history = arrayOfNulls<GrandExchangeOffer>(5)
|
||||
val offerRecords = arrayOfNulls<OfferRecord>(6)
|
||||
|
||||
override fun login(player: Player) {
|
||||
val instance = GrandExchangeRecords(player)
|
||||
player.setAttribute("ge-records", instance)
|
||||
}
|
||||
|
||||
override fun parsePlayer(player: Player, data: JSONObject) {
|
||||
/**
|
||||
* Parse history from JSON
|
||||
*/
|
||||
val historyRaw = data["ge-history"]
|
||||
if(historyRaw != null){
|
||||
val history = historyRaw as JSONArray
|
||||
for (i in history.indices) {
|
||||
val offer = history[i] as JSONObject
|
||||
val o = GrandExchangeOffer()
|
||||
o.itemID = offer["itemId"].toString().toInt()
|
||||
o.sell = offer["isSell"] as Boolean
|
||||
o.totalCoinExchange = (offer["totalCoinExchange"].toString().toInt())
|
||||
o.completedAmount = (offer["completedAmount"].toString().toInt())
|
||||
getInstance(player).history[i] = o
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read offers from the database
|
||||
*/
|
||||
val needsIndex = ArrayDeque<GrandExchangeOffer>()
|
||||
val instance = getInstance(player)
|
||||
|
||||
GEDB.run { conn ->
|
||||
val stmt = conn.createStatement()
|
||||
val offer_records = stmt.executeQuery("SELECT * from player_offers where player_uid = ${player.details.uid} AND offer_state < 6")
|
||||
|
||||
while (offer_records.next()) {
|
||||
val offer = GrandExchangeOffer.fromQuery(offer_records)
|
||||
if (offer.index == -1) //used to index old (converted from JSON) offers
|
||||
needsIndex.push(offer)
|
||||
else
|
||||
instance.offerRecords[offer.index] = OfferRecord(offer.uid, offer.index)
|
||||
}
|
||||
stmt.close()
|
||||
}
|
||||
|
||||
if (needsIndex.isNotEmpty()) {
|
||||
for ((index, offer) in offerRecords.withIndex()) {
|
||||
if (offer == null) {
|
||||
val o = needsIndex.pop()
|
||||
o.index = index
|
||||
instance.offerRecords[o.index] = OfferRecord(o.uid, o.index)
|
||||
o.update() //write the new index to the database
|
||||
}
|
||||
}
|
||||
|
||||
while(needsIndex.isNotEmpty()) //If we enter this loop, there were more offers than can fit inside the 6 slots. This should never happen - at least, not anymore. Can't speak for JSON offers.
|
||||
{
|
||||
val o = needsIndex.pop()
|
||||
SystemLogger.logGE("[WARN] PLAYER HAD EXTRA OFFER - RECOMMEND IMMEDIATE REFUND OF CONTENTS -> OFFER UID: ${o.uid}")
|
||||
SystemLogger.logGE("[WARN] AS PER ABOVE MESSAGE, REFUND CONTENTS OF OFFER AND MANUALLY SET offer_state = 6")
|
||||
}
|
||||
}
|
||||
|
||||
instance.init()
|
||||
}
|
||||
|
||||
override fun savePlayer(player: Player, save: JSONObject) {
|
||||
/**
|
||||
* Save history to JSON
|
||||
*/
|
||||
val history = JSONArray()
|
||||
getInstance(player).history.map {
|
||||
if(it != null){
|
||||
val historyEntry = JSONObject()
|
||||
historyEntry["isSell"] = it.sell
|
||||
historyEntry["itemId"] = it.itemID.toString()
|
||||
historyEntry["totalCoinExchange"] = it.totalCoinExchange.toString()
|
||||
historyEntry["completedAmount"] = it.completedAmount.toString()
|
||||
history.add(historyEntry)
|
||||
}
|
||||
}
|
||||
save["ge-history"] = history
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the collection box.
|
||||
*/
|
||||
fun openCollectionBox() {
|
||||
if (!player!!.bankPinManager.isUnlocked) {
|
||||
player.bankPinManager.openType(3)
|
||||
return
|
||||
}
|
||||
player.interfaceManager.openComponent(Components.STOCKCOLLECT_109)
|
||||
player.packetDispatch.sendIfaceSettings(6, 18, 109, 0, 2)
|
||||
player.packetDispatch.sendIfaceSettings(6, 23, 109, 0, 2)
|
||||
player.packetDispatch.sendIfaceSettings(6, 28, 109, 0, 2)
|
||||
player.packetDispatch.sendIfaceSettings(6, 36, 109, 0, 2)
|
||||
player.packetDispatch.sendIfaceSettings(6, 44, 109, 0, 2)
|
||||
player.packetDispatch.sendIfaceSettings(6, 52, 109, 0, 2)
|
||||
|
||||
visualizeRecords()
|
||||
}
|
||||
|
||||
fun visualizeRecords()
|
||||
{
|
||||
GEDB.run { conn ->
|
||||
val stmt = conn.createStatement()
|
||||
|
||||
for (record in offerRecords) {
|
||||
if (record != null) {
|
||||
val offer_raw = stmt.executeQuery("select * from player_offers where uid = ${record.uid}")
|
||||
if (offer_raw.next()) {
|
||||
val offer = GrandExchangeOffer.fromQuery(offer_raw)
|
||||
if (offer.offerState == OfferState.REMOVED) continue
|
||||
offer.index = record.slot
|
||||
offer.visualize(player)
|
||||
}
|
||||
}
|
||||
}
|
||||
stmt.close()
|
||||
}
|
||||
}
|
||||
|
||||
fun getOffer(index: Int) : GrandExchangeOffer?
|
||||
{
|
||||
if(index == -1) return getOffer(null)
|
||||
return getOffer(offerRecords[index])
|
||||
}
|
||||
|
||||
fun getOffer(record: OfferRecord?) : GrandExchangeOffer?
|
||||
{
|
||||
record ?: return null
|
||||
var offerToReturn: GrandExchangeOffer? = null
|
||||
GEDB.run { conn ->
|
||||
val stmt = conn.createStatement()
|
||||
val offer_raw = stmt.executeQuery("select * from player_offers where uid = ${record.uid}")
|
||||
if (offer_raw.next()) {
|
||||
val offer = GrandExchangeOffer.fromQuery(offer_raw)
|
||||
offer.index = record.slot
|
||||
stmt.close()
|
||||
offerToReturn = offer
|
||||
}
|
||||
stmt.close()
|
||||
}
|
||||
return offerToReturn
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the history log.
|
||||
*
|
||||
* @param p The player to open it for.
|
||||
*/
|
||||
fun openHistoryLog(p: Player) {
|
||||
p.interfaceManager.open(Component(643))
|
||||
for (i in history.indices) {
|
||||
val o: GrandExchangeOffer? = history[i]
|
||||
if (o == null) {
|
||||
for (j in 0..3) {
|
||||
p.packetDispatch.sendString("-", 643, 25 + i + j * 5)
|
||||
}
|
||||
continue
|
||||
}
|
||||
p.packetDispatch.sendString(if (o.sell) "You sold" else "You bought", 643, 25 + i)
|
||||
p.packetDispatch.sendString(
|
||||
NumberFormat.getNumberInstance(Locale.US).format(o.completedAmount.toLong()),
|
||||
643,
|
||||
30 + i
|
||||
)
|
||||
p.packetDispatch.sendString(ItemDefinition.forId(o.itemID).name, 643, 35 + i)
|
||||
p.packetDispatch.sendString(
|
||||
NumberFormat.getNumberInstance(Locale.US).format(o.totalCoinExchange.toLong()) + " gp", 643, 40 + i
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the grand exchange.
|
||||
*/
|
||||
fun init() {
|
||||
// Were trades made while gone?
|
||||
var updated = false
|
||||
for (record in offerRecords) {
|
||||
if (record != null) {
|
||||
val offer = getOffer(record) ?: continue
|
||||
if (!updated && (offer.withdraw[0] != null || offer.withdraw[1] != null)) {
|
||||
updated = true
|
||||
}
|
||||
offer.visualize(player)
|
||||
}
|
||||
}
|
||||
if (updated) {
|
||||
sendMessage(player!!, "You have items from the Grand Exchange waiting in your collection box.")
|
||||
}
|
||||
}
|
||||
|
||||
fun hasActiveOffer(): Boolean {
|
||||
for (i in offerRecords) {
|
||||
if (i != null)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the grand exchange.
|
||||
*
|
||||
* @return the formatted offer for the SQL database.
|
||||
*/
|
||||
fun format(): String? {
|
||||
var log = ""
|
||||
for (record in offerRecords) {
|
||||
if (record != null) {
|
||||
val offer = getOffer(record) ?: continue
|
||||
log += offer.itemID.toString() + "," + offer.amount + "," + offer.sell + "|"
|
||||
}
|
||||
}
|
||||
if (log.isNotEmpty() && log[log.length - 1] == '|') {
|
||||
log = log.substring(0, log.length - 1)
|
||||
}
|
||||
return log
|
||||
}
|
||||
|
||||
data class OfferRecord(val uid: Long, val slot: Int)
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun getInstance(player: Player? = null): GrandExchangeRecords
|
||||
{
|
||||
return player?.getAttribute("ge-records", GrandExchangeRecords()) ?: GrandExchangeRecords()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package core.game.ge;
|
||||
|
||||
/**
|
||||
* Represents the state of a Grand Exchange offer.
|
||||
* @author Emperor
|
||||
*/
|
||||
public enum OfferState {
|
||||
|
||||
/**
|
||||
* The player is still constructing the offer.
|
||||
*/
|
||||
PENDING,
|
||||
|
||||
/**
|
||||
* The player has confirmed the offer (the offer is being dispatched).
|
||||
*/
|
||||
REGISTERED,
|
||||
|
||||
/**
|
||||
* The offer has been aborted.
|
||||
*/
|
||||
ABORTED,
|
||||
|
||||
/**
|
||||
* The offer has been updated.
|
||||
*/
|
||||
UPDATED,
|
||||
|
||||
/**
|
||||
* The offer is completed.
|
||||
*/
|
||||
COMPLETED,
|
||||
|
||||
/**
|
||||
* The offer is outdated.
|
||||
*/
|
||||
OUTDATED,
|
||||
|
||||
/**
|
||||
* The offer has been removed.
|
||||
*/
|
||||
REMOVED;
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package core.game.ge
|
||||
|
||||
import core.api.itemDefinition
|
||||
import java.sql.ResultSet
|
||||
|
||||
object PriceIndex {
|
||||
@JvmStatic fun canTrade(id: Int): Boolean {
|
||||
var canTrade = false
|
||||
|
||||
GEDB.run { conn ->
|
||||
val stmt = conn.prepareStatement(EXISTS_QUERY)
|
||||
stmt.setInt(1, id)
|
||||
val res = stmt.executeQuery()
|
||||
if (res.next()) {
|
||||
canTrade = res.getInt(1) == 1
|
||||
}
|
||||
}
|
||||
|
||||
return canTrade
|
||||
}
|
||||
|
||||
fun banItem(id: Int) {
|
||||
GEDB.run { conn ->
|
||||
val stmt = conn.prepareStatement(REMOVE_QUERY)
|
||||
stmt.setInt(1, id)
|
||||
stmt.execute()
|
||||
}
|
||||
}
|
||||
|
||||
fun allowItem(id: Int) {
|
||||
if(canTrade(id)) return
|
||||
|
||||
GEDB.run { conn ->
|
||||
val stmt = conn.prepareStatement(INSERT_QUERY)
|
||||
stmt.setInt(1, id)
|
||||
stmt.setInt(2, itemDefinition(id).getAlchemyValue(true))
|
||||
stmt.setInt(3, itemDefinition(id).getAlchemyValue(true))
|
||||
stmt.setInt(4, 0)
|
||||
stmt.setInt(5, 0)
|
||||
stmt.execute()
|
||||
}
|
||||
}
|
||||
|
||||
fun getValue(id: Int): Int {
|
||||
var value = itemDefinition(id).getAlchemyValue(true)
|
||||
GEDB.run { conn ->
|
||||
val stmt = conn.prepareStatement(GET_VALUE_QUERY)
|
||||
stmt.setInt(1, id)
|
||||
val res = stmt.executeQuery()
|
||||
if (res.next()) {
|
||||
value = res.getInt(1)
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
fun addTrade(id: Int, amount: Int, pricePerUnit: Int) {
|
||||
val oldInfo = getInfo(id) ?: return
|
||||
val newInfo = oldInfo.copy()
|
||||
|
||||
val volumeResetThreshold =
|
||||
if (pricePerUnit >= 1000000) 500
|
||||
else if (pricePerUnit >= 100000) 1000
|
||||
else if (pricePerUnit >= 25000) 2500
|
||||
else 10000
|
||||
|
||||
newInfo.totalValue += (amount * pricePerUnit)
|
||||
newInfo.uniqueTrades += amount
|
||||
newInfo.lastUpdate = System.currentTimeMillis()
|
||||
newInfo.currentValue = (newInfo.totalValue / newInfo.uniqueTrades).toInt()
|
||||
|
||||
if (newInfo.uniqueTrades > volumeResetThreshold) {
|
||||
val newAmt = (0.1 * volumeResetThreshold).toInt()
|
||||
newInfo.uniqueTrades = newAmt
|
||||
newInfo.totalValue = newAmt.toLong() * newInfo.currentValue
|
||||
}
|
||||
|
||||
updateInfo(newInfo)
|
||||
}
|
||||
|
||||
private fun updateInfo(newInfo: PriceInfo) {
|
||||
GEDB.run { conn ->
|
||||
val stmt = conn.prepareStatement(UPDATE_QUERY)
|
||||
stmt.setInt(1, newInfo.currentValue)
|
||||
stmt.setLong(2, newInfo.totalValue)
|
||||
stmt.setInt(3, newInfo.uniqueTrades)
|
||||
stmt.setLong(4, newInfo.lastUpdate)
|
||||
stmt.setInt(5, newInfo.itemId)
|
||||
stmt.execute()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInfo(id: Int): PriceInfo? {
|
||||
var priceInfo: PriceInfo? = null
|
||||
|
||||
GEDB.run { conn ->
|
||||
val stmt = conn.prepareStatement(SELECT_QUERY)
|
||||
stmt.setInt(1, id)
|
||||
val res = stmt.executeQuery()
|
||||
if (res.next()) {
|
||||
priceInfo = PriceInfo.fromQuery(res)
|
||||
}
|
||||
}
|
||||
|
||||
return priceInfo
|
||||
}
|
||||
|
||||
const val SELECT_QUERY = "SELECT * FROM price_index WHERE item_id = ?;"
|
||||
const val UPDATE_QUERY = "UPDATE price_index SET value = ?, total_value = ?, unique_trades = ?, last_update = ? WHERE item_id = ?;"
|
||||
const val EXISTS_QUERY = "SELECT EXISTS(SELECT 1 FROM price_index WHERE item_id = ?);"
|
||||
const val REMOVE_QUERY = "DELETE FROM price_index WHERE item_id = ?;"
|
||||
const val INSERT_QUERY = "INSERT INTO price_index (item_id, value, total_value, unique_trades, last_update) VALUES (?,?,?,?,?);"
|
||||
const val GET_VALUE_QUERY = "SELECT value FROM price_index WHERE item_id = ?;"
|
||||
}
|
||||
|
||||
data class PriceInfo(
|
||||
var itemId: Int,
|
||||
var currentValue: Int,
|
||||
var totalValue: Long,
|
||||
var uniqueTrades: Int,
|
||||
var lastUpdate: Long
|
||||
) {
|
||||
fun copy() : PriceInfo {
|
||||
return PriceInfo(itemId, currentValue, totalValue, uniqueTrades, lastUpdate)
|
||||
}
|
||||
companion object {
|
||||
fun fromQuery(result: ResultSet) : PriceInfo {
|
||||
return PriceInfo(
|
||||
result.getInt(1),
|
||||
result.getInt(2),
|
||||
result.getLong(3),
|
||||
result.getInt(4),
|
||||
result.getLong(5)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package core.game.global;
|
||||
|
||||
import core.game.container.Container;
|
||||
import core.game.dialogue.FacialExpression;
|
||||
import core.game.dialogue.FacialExpression;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
|
||||
/**
|
||||
* Represents a utility class for purchasing skillcapes.
|
||||
* @author Vexia
|
||||
*/
|
||||
public final class Skillcape {
|
||||
|
||||
/**
|
||||
* Represents the amount needed to purchase a cape.
|
||||
*/
|
||||
private static final Item COINS = new Item(995, 99000);
|
||||
|
||||
/**
|
||||
* Represents the skillcapes to purchase.
|
||||
*/
|
||||
public static final int[] SKILLCAPES = { 9747, 9753, 9750, 9768, 9756, 9759, 9762, 9801, 9807, 9783, 9798, 9804, 9780, 9795, 9792, 9774, 9771, 9777, 9786, 9810, 9765, 9948, 9789, 12169 };
|
||||
|
||||
/**
|
||||
* Method used to purchase a cape of accomplisment.
|
||||
* @param player the player.
|
||||
* @param skill the skill.
|
||||
* @return {@code True} if purchased.
|
||||
*/
|
||||
public static boolean purchase(final Player player, final int skill) {
|
||||
if (!isMaster(player, skill)) {
|
||||
return false;
|
||||
}
|
||||
if (player.getInventory().freeSlots() < 2) {
|
||||
player.getDialogueInterpreter().sendDialogues(player, FacialExpression.HALF_GUILTY, "Sorry, I don't seem to have inventory space.");
|
||||
return false;
|
||||
}
|
||||
if (!player.getInventory().containsItem(COINS)) {
|
||||
player.getDialogueInterpreter().sendDialogues(player, FacialExpression.HALF_GUILTY, "Sorry, I don't seem to have enough coins with", "me at this time.");
|
||||
return false;
|
||||
}
|
||||
if (player.getInventory().remove(COINS)) {
|
||||
return player.getInventory().add(getItems(player, skill));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to trim the players skillcapes.
|
||||
* @param player the player.
|
||||
*/
|
||||
public static void trim(final Player player) {
|
||||
final Container[] containers = new Container[] { player.getInventory(), player.getEquipment(), player.getBank() };
|
||||
int skill = -1;
|
||||
for (Container container : containers) {
|
||||
for (Item item : container.toArray()) {
|
||||
if (item == null || item.getId() < 9700) {
|
||||
continue;
|
||||
}
|
||||
skill = getCapeIndex(item);
|
||||
if (skill != -1) {
|
||||
container.replace(new Item(getTrimmed(skill).getId(), item.getAmount()), item.getSlot());
|
||||
skill = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the player has the appropriate level.
|
||||
* @param player the player.
|
||||
* @param skill the skill.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public static boolean isMaster(final Player player, final int skill) {
|
||||
return player.getSkills().getStaticLevel(skill) == 99;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the items to purchase.
|
||||
* @param player the player.
|
||||
* @param skill the skill.
|
||||
* @return {@code Items} to buy.
|
||||
*/
|
||||
public static Item[] getItems(final Player player, final int skill) {
|
||||
return new Item[] { new Item(SKILLCAPES[skill] + (player.getSkills().getMasteredSkills() > 1 ? 1 : 0)), new Item(SKILLCAPES[skill] + 2) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the trimmed item.
|
||||
* @param skill the skill.
|
||||
* @return the trimmed cape.
|
||||
*/
|
||||
public static Item getTrimmed(final int skill) {
|
||||
return new Item(SKILLCAPES[skill] + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cape index by the item.
|
||||
* @param item the item.
|
||||
* @return the skill index, if not (-1).
|
||||
*/
|
||||
public static int getCapeIndex(final Item item) {
|
||||
for (int i = 0; i < SKILLCAPES.length; i++) {
|
||||
if (SKILLCAPES[i] == item.getId()) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
package core.game.global.action;
|
||||
|
||||
import core.game.dialogue.DialoguePlugin;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.scenery.Scenery;
|
||||
import core.game.system.task.Pulse;
|
||||
import core.game.world.GameWorld;
|
||||
import core.game.world.map.Direction;
|
||||
import core.game.world.map.Location;
|
||||
import core.game.world.map.RegionManager;
|
||||
import core.game.world.update.flag.context.Animation;
|
||||
|
||||
/**
|
||||
* Handles a ladder climbing reward.
|
||||
* @author Emperor
|
||||
*/
|
||||
public final class ClimbActionHandler {
|
||||
|
||||
/**
|
||||
* Represents the climb up animation of ladders.
|
||||
*/
|
||||
public static final Animation CLIMB_UP = new Animation(828);
|
||||
|
||||
/**
|
||||
* Represents the climb down animation of ladders.
|
||||
*/
|
||||
public static final Animation CLIMB_DOWN = new Animation(827);
|
||||
|
||||
/**
|
||||
* The climb dialogue.
|
||||
*/
|
||||
public static DialoguePlugin CLIMB_DIALOGUE = new ClimbDialogue();
|
||||
|
||||
/**
|
||||
* Handles the climbing of a rope.
|
||||
*
|
||||
* @param player The player.
|
||||
* @param object The rope object.
|
||||
* @param option The option.
|
||||
*/
|
||||
public static void climbRope(Player player, Scenery object, String option) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the climbing of a trap door.
|
||||
*
|
||||
* @param player The player.
|
||||
* @param object The trap door object.
|
||||
* @param option The option.
|
||||
*/
|
||||
public static void climbTrapdoor(Player player, Scenery object, String option) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the climbing of a ladder.
|
||||
*
|
||||
* @param player The player.
|
||||
* @param startLadder The scenery.
|
||||
* @param option The option.
|
||||
* @return True if successfully climbed
|
||||
*/
|
||||
public static boolean climbLadder(Player player, Scenery startLadder, String option) {
|
||||
Scenery endLadder = null;
|
||||
Animation animation = CLIMB_UP;
|
||||
if (startLadder.getName().startsWith("Stair")) {
|
||||
animation = null;
|
||||
}
|
||||
if (SpecialLadders.getDestination(startLadder.getLocation()) != null) {
|
||||
Location destination = SpecialLadders.getDestination(startLadder.getLocation());
|
||||
climb(player, animation, destination);
|
||||
if(SpecialLadders.getSpecialLadder(startLadder.getLocation()) != null) {
|
||||
SpecialLadders.getSpecialLadder(startLadder.getLocation()).checkAchievement(player);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
switch (option) {
|
||||
case "climb-up":
|
||||
endLadder = getLadder(startLadder, false);
|
||||
break;
|
||||
case "climb-down":
|
||||
if (startLadder.getName().equals("Trapdoor")) {
|
||||
animation = CLIMB_DOWN;
|
||||
}
|
||||
endLadder = getLadder(startLadder, true);
|
||||
break;
|
||||
case "climb":
|
||||
Scenery upperLadder = getLadder(startLadder, false);
|
||||
Scenery downLadder = getLadder(startLadder, true);
|
||||
if (upperLadder == null && downLadder != null) {
|
||||
return climbLadder(player, startLadder, "climb-down");
|
||||
}
|
||||
if (upperLadder != null && downLadder == null) {
|
||||
return climbLadder(player, startLadder, "climb-up");
|
||||
}
|
||||
DialoguePlugin dialogue = CLIMB_DIALOGUE.newInstance(player);
|
||||
if (dialogue != null && dialogue.open(startLadder)) {
|
||||
player.getDialogueInterpreter().setDialogue(dialogue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Location destination = endLadder != null ? getDestination(endLadder) : null;
|
||||
if (endLadder == null || destination == null) {
|
||||
player.getPacketDispatch().sendMessage("The ladder doesn't seem to lead anywhere.");
|
||||
return false;
|
||||
}
|
||||
climb(player, animation, destination);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the teleport destination.
|
||||
*
|
||||
* @param object The object to teleport to.
|
||||
* @return The teleport destination.
|
||||
*/
|
||||
public static Location getDestination(Scenery object) {
|
||||
int sizeX = object.getDefinition().sizeX;
|
||||
int sizeY = object.getDefinition().sizeY;
|
||||
if (object.getRotation() % 2 != 0) {
|
||||
int switcher = sizeX;
|
||||
sizeX = sizeY;
|
||||
sizeY = switcher;
|
||||
}
|
||||
Direction dir = Direction.forWalkFlag(object.getDefinition().getWalkingFlag(), object.getRotation());
|
||||
if (dir != null) {
|
||||
return getDestination(object, sizeX, sizeY, dir, 0);
|
||||
}
|
||||
switch (object.getRotation()) {
|
||||
case 0:
|
||||
return getDestination(object, sizeX, sizeY, Direction.SOUTH, 0);
|
||||
case 1:
|
||||
return getDestination(object, sizeX, sizeY, Direction.EAST, 0);
|
||||
case 2:
|
||||
return getDestination(object, sizeX, sizeY, Direction.NORTH, 0);
|
||||
case 3:
|
||||
return getDestination(object, sizeX, sizeY, Direction.WEST, 0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the destination for the given object.
|
||||
*
|
||||
* @param object The object.
|
||||
* @param dir The preferred direction from the object.
|
||||
* @return The teleporting destination.
|
||||
*/
|
||||
private static Location getDestination(Scenery object, int sizeX, int sizeY, Direction dir, int count) {
|
||||
Location loc = object.getLocation();
|
||||
if (dir.toInteger() % 2 != 0) {
|
||||
int x = dir.getStepX();
|
||||
if (x > 0) {
|
||||
x *= sizeX;
|
||||
}
|
||||
for (int y = 0; y < sizeY; y++) {
|
||||
Location l = loc.transform(x, y, 0);
|
||||
if (RegionManager.isTeleportPermitted(l) && dir.canMove(l)) {
|
||||
return l;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int y = dir.getStepY();
|
||||
if (y > 0) {
|
||||
y *= sizeY;
|
||||
}
|
||||
for (int x = 0; x < sizeX; x++) {
|
||||
Location l = loc.transform(x, y, 0);
|
||||
if (RegionManager.isTeleportPermitted(l) && dir.canMove(l)) {
|
||||
return l;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count == 3) {
|
||||
return null;
|
||||
}
|
||||
return getDestination(object, sizeX, sizeY, Direction.get((dir.toInteger() + 1) % 4), count + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the climbing reward.
|
||||
*
|
||||
* @param player The player.
|
||||
* @param animation The climbing animation.
|
||||
* @param destination The destination.
|
||||
*/
|
||||
public static void climb(final Player player, Animation animation, final Location destination, final String... messages) {
|
||||
player.lock(2);
|
||||
player.animate(animation);
|
||||
GameWorld.getPulser().submit(new Pulse(1) {
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
player.getProperties().setTeleportLocation(destination);
|
||||
for (String message : messages) {
|
||||
player.getPacketDispatch().sendMessage(message);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ladder the object leads to.
|
||||
*
|
||||
* @param object The ladder object.
|
||||
* @param down If the player is going down a floor.
|
||||
* @return The ladder the current ladder object leads to.
|
||||
*/
|
||||
private static Scenery getLadder(Scenery object, boolean down) {
|
||||
int mod = down ? -1 : 1;
|
||||
Scenery ladder = RegionManager.getObject(object.getLocation().transform(0, 0, mod));
|
||||
if (ladder == null || !isLadder(ladder)) {
|
||||
if (ladder != null && ladder.getName().equals(object.getName())) {
|
||||
ladder = RegionManager.getObject(ladder.getLocation().transform(0, 0, mod));
|
||||
if (ladder != null) {
|
||||
return ladder;
|
||||
}
|
||||
}
|
||||
ladder = findLadder(object.getLocation().transform(0, 0, mod));
|
||||
if (ladder == null) {
|
||||
ladder = RegionManager.getObject(object.getLocation().transform(0, mod * -6400, 0));
|
||||
if (ladder == null) {
|
||||
ladder = findLadder(object.getLocation().transform(0, mod * -6400, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ladder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a ladder (by searching a 10x10 area around the given location).
|
||||
*
|
||||
* @param l The location.
|
||||
* @return The ladder.
|
||||
*/
|
||||
private static Scenery findLadder(Location l) {
|
||||
for (int x = -5; x < 6; x++) {
|
||||
for (int y = -5; y < 6; y++) {
|
||||
Scenery object = RegionManager.getObject(l.transform(x, y, 0));
|
||||
if (object != null && isLadder(object)) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the object is a ladder.
|
||||
*
|
||||
* @param object The object.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
private static boolean isLadder(Scenery object) {
|
||||
for (String option : object.getDefinition().getOptions()) {
|
||||
if (option != null && (option.contains("Climb"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return object.getName().equals("Trapdoor");
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the dialogue plugin used for climbing stairs or a ladder.
|
||||
*
|
||||
* @author 'Vexia
|
||||
* @version 1.0
|
||||
*/
|
||||
static final class ClimbDialogue extends DialoguePlugin {
|
||||
|
||||
/**
|
||||
* Represents the climbing dialogue id.
|
||||
*/
|
||||
public static final int ID = 8 << 16;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code ClimbDialogue} {@code Object}.
|
||||
*/
|
||||
public ClimbDialogue() {
|
||||
/**
|
||||
* empty.
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code ClimbDialogue} {@code Object}.
|
||||
*
|
||||
* @param player the player.
|
||||
*/
|
||||
public ClimbDialogue(final Player player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DialoguePlugin newInstance(Player player) {
|
||||
return new ClimbDialogue(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the object to use.
|
||||
*/
|
||||
private Scenery object;
|
||||
|
||||
@Override
|
||||
public boolean open(Object... args) {
|
||||
object = (Scenery) args[0];
|
||||
interpreter.sendOptions("What would you like to do?", "Climb Up.", "Climb Down.");
|
||||
stage = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(int interfaceId, int buttonId) {
|
||||
switch (stage) {
|
||||
case 0:
|
||||
switch (buttonId) {
|
||||
case 1:
|
||||
player.lock(1);
|
||||
GameWorld.getPulser().submit(new Pulse(1) {
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
climbLadder(player, object, "climb-up");
|
||||
return true;
|
||||
}
|
||||
});
|
||||
end();
|
||||
break;
|
||||
case 2:
|
||||
player.lock(1);
|
||||
GameWorld.getPulser().submit(new Pulse(1) {
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
climbLadder(player, object, "climb-down");
|
||||
return true;
|
||||
}
|
||||
});
|
||||
end();
|
||||
break;
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getIds() {
|
||||
return new int[]{ID};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package core.game.global.action;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
|
||||
/**
|
||||
* Handles a digging reward.
|
||||
* @author Emperor
|
||||
*/
|
||||
public interface DigAction {
|
||||
|
||||
/**
|
||||
* Runs the digging reward.
|
||||
* @param player The player.
|
||||
*/
|
||||
void run(Player player);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package core.game.global.action;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.system.communication.CommunicationInfo;
|
||||
import content.global.handlers.item.SpadeDigListener;
|
||||
import core.tools.SystemLogger;
|
||||
import core.game.system.task.Pulse;
|
||||
import core.game.world.GameWorld;
|
||||
import core.game.world.map.Location;
|
||||
import core.game.world.update.flag.context.Animation;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Handles digging with a spade.
|
||||
* @author Emperor
|
||||
*/
|
||||
public final class DigSpadeHandler {
|
||||
|
||||
/**
|
||||
* The digging actions.
|
||||
*/
|
||||
private static final Map<Location, DigAction> ACTIONS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The digging animation.
|
||||
*/
|
||||
public static final Animation ANIMATION = Animation.create(830);
|
||||
|
||||
/**
|
||||
* Handles a digging reward.
|
||||
* @param player The player.
|
||||
* @return {@code True} if the reward got handled.
|
||||
*/
|
||||
public static boolean dig(final Player player) {
|
||||
final DigAction action = ACTIONS.get(player.getLocation());
|
||||
player.animate(ANIMATION);
|
||||
player.lock(1);
|
||||
|
||||
if(SpadeDigListener.runListener(player.getLocation(), player)){
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action != null) {
|
||||
GameWorld.getPulser().submit(new Pulse(1, player) {
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
action.run(player);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new digging reward.
|
||||
* @param location The location to dig on.
|
||||
* @param action The reward.
|
||||
* @return {@code True} if the reward got registered.
|
||||
*/
|
||||
public static boolean register(Location location, DigAction action) {
|
||||
if (ACTIONS.containsKey(location)) {
|
||||
SystemLogger.logErr(CommunicationInfo.class, "Already contained dig reward for location " + location + ".");
|
||||
return false;
|
||||
}
|
||||
ACTIONS.put(location, action);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,515 @@
|
||||
package core.game.global.action;
|
||||
|
||||
import core.game.node.entity.Entity;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.entity.player.link.audio.Audio;
|
||||
import core.game.node.entity.player.link.diary.DiaryType;
|
||||
import core.game.node.scenery.Constructed;
|
||||
import core.game.node.scenery.Scenery;
|
||||
import core.game.node.scenery.SceneryBuilder;
|
||||
import core.game.system.task.Pulse;
|
||||
import core.game.world.map.Direction;
|
||||
import core.game.world.map.Location;
|
||||
import core.game.world.map.RegionManager;
|
||||
import core.game.world.map.path.Pathfinder;
|
||||
import kotlin.Unit;
|
||||
import core.game.system.config.DoorConfigLoader;
|
||||
import core.game.world.GameWorld;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* Handles door actions.
|
||||
*
|
||||
* @author Emperor
|
||||
*/
|
||||
public final class DoorActionHandler {
|
||||
|
||||
/**
|
||||
* The charge indicating the door is already in use.
|
||||
*/
|
||||
private static final int IN_USE_CHARGE = 88;
|
||||
|
||||
/**
|
||||
* Handles a door reward.
|
||||
*
|
||||
* @param player The player.
|
||||
* @param object The object.
|
||||
*/
|
||||
public static void handleDoor(final Player player, final Scenery object) {
|
||||
final Scenery second = (object.getId() == 1530 || object.getId() == 1531) ? null : getSecondDoor(object, player);
|
||||
Scenery o = null;
|
||||
if (object instanceof Constructed && (o = ((Constructed) object).getReplaced()) != null) {
|
||||
player.getAudioManager().send(new Audio(43));
|
||||
SceneryBuilder.replace(object, o);
|
||||
if (second instanceof Constructed && (o = ((Constructed) second).getReplaced()) != null) {
|
||||
SceneryBuilder.replace(second, o);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (object.getDefinition().hasAction("close")) {
|
||||
if (second != null) {
|
||||
player.getPacketDispatch().sendMessage("The doors appear to be stuck.");
|
||||
return;
|
||||
}
|
||||
DoorConfigLoader.Door d = DoorConfigLoader.Companion.forId(object.getId());
|
||||
if (d == null) {
|
||||
player.getPacketDispatch().sendMessage("The door appears to be stuck.");
|
||||
return;
|
||||
}
|
||||
int firstDir = (object.getRotation() + 3) % 4;
|
||||
Point p = getCloseRotation(object);
|
||||
Location firstLoc = object.getLocation().transform((int) p.getX(), (int) p.getY(), 0);
|
||||
SceneryBuilder.replace(object, object.transform(d.getReplaceId(), firstDir, firstLoc));
|
||||
return;
|
||||
}
|
||||
DoorConfigLoader.Door d = DoorConfigLoader.Companion.forId(object.getId());
|
||||
if (d == null) {
|
||||
handleAutowalkDoor(player, object);
|
||||
return;
|
||||
}
|
||||
if (d.isMetal()) {
|
||||
player.getAudioManager().send(new Audio(71));
|
||||
} else {
|
||||
player.getAudioManager().send(new Audio(81));
|
||||
}
|
||||
if (second != null) {
|
||||
DoorConfigLoader.Door s = DoorConfigLoader.Companion.forId(second.getId());
|
||||
open(object, second, d.getReplaceId(), s == null ? second.getId() : s.getReplaceId(), true, 500, d.isFence());
|
||||
return;
|
||||
}
|
||||
open(object, null, d.getReplaceId(), -1, true, 500, d.isFence());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the opening and walking through a door.
|
||||
*
|
||||
* @param entity The entity walking through the door.
|
||||
* @param object The door object.
|
||||
* @return
|
||||
*/
|
||||
public static boolean handleAutowalkDoor(final Entity entity, final Scenery object, final Location endLocation) {
|
||||
if (object.getCharge() == IN_USE_CHARGE) {
|
||||
return false;
|
||||
}
|
||||
final Scenery second = (object.getId() == 3) ? null : getSecondDoor(object, entity);
|
||||
entity.lock(4);
|
||||
final Location loc = entity.getLocation();
|
||||
if (entity instanceof Player) {
|
||||
((Player) entity).getAudioManager().send(new Audio(3419));
|
||||
entity.asPlayer().logoutListeners.put("autowalk", player -> {
|
||||
player.setLocation(loc);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
GameWorld.getPulser().submit(new Pulse(1) {
|
||||
boolean opened = false;
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
if (!opened) {
|
||||
open(object, second, object.getId(), second == null ? -1 : second.getId(), false, 2, false);
|
||||
entity.getWalkingQueue().reset();
|
||||
entity.getWalkingQueue().addPath(endLocation.getX(), endLocation.getY());
|
||||
opened = true;
|
||||
|
||||
// Set the door in_use
|
||||
object.setCharge(IN_USE_CHARGE);
|
||||
if (second != null) {
|
||||
second.setCharge(IN_USE_CHARGE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (entity instanceof Player) {
|
||||
Player player = entity.asPlayer();
|
||||
if (object.getId() == 2112 && player.getLocation().withinDistance(new Location(3046, 9756, 0), 10)) {
|
||||
player.getAchievementDiaryManager().finishTask(player, DiaryType.FALADOR, 2, 6);
|
||||
}
|
||||
|
||||
// pass the al kharid gate
|
||||
if (object.getId() == 35549 || object.getId() == 35551 && player.getViewport().getRegion().getId() == 13106) {
|
||||
player.getAchievementDiaryManager().finishTask(player, DiaryType.LUMBRIDGE, 0, 4);
|
||||
}
|
||||
|
||||
// Search the shed in Lumbridge Swamp
|
||||
if (object.getId() == 2406 && player.getLocation().withinDistance(Location.create(3202,3169,0))) {
|
||||
player.getAchievementDiaryManager().finishTask(player, DiaryType.LUMBRIDGE, 1, 6);
|
||||
}
|
||||
|
||||
entity.asPlayer().logoutListeners.remove("autowalk");
|
||||
}
|
||||
|
||||
// Reset door to inactive
|
||||
object.setCharge(1000);
|
||||
if (second != null) {
|
||||
second.setCharge(1000);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method wrapper for handling the auto walk door.
|
||||
*
|
||||
* @param entity the entity.
|
||||
* @param object the object.
|
||||
*/
|
||||
public static boolean handleAutowalkDoor(final Entity entity, final Scenery object) {
|
||||
return handleAutowalkDoor(entity, object, getEndLocation(entity, object));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the end location to walk to.
|
||||
*
|
||||
* @param entity the entity.
|
||||
* @param object the object.
|
||||
* @return the end location.
|
||||
*/
|
||||
public static Location getEndLocation(Entity entity, Scenery object){
|
||||
return getEndLocation(entity,object,false);
|
||||
}
|
||||
public static Location getEndLocation(Entity entity, Scenery object, Boolean isAutoWalk) {
|
||||
Location l = object.getLocation();
|
||||
switch (object.getRotation()) {
|
||||
case 0:
|
||||
if (entity.getLocation().getX() >= l.getX()) {
|
||||
l = l.transform(-1, 0, 0);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (entity.getLocation().getY() <= l.getY()) {
|
||||
l = l.transform(0, 1, 0);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (entity.getLocation().getX() <= l.getX()) {
|
||||
l = l.transform(1, 0, 0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (entity.getLocation().getY() >= l.getY()) {
|
||||
l = l.transform(0, -1, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the destination for the door.
|
||||
*
|
||||
* @param door The door.
|
||||
* @return The destination location.
|
||||
*/
|
||||
public static Location getDestination(Entity entity, Scenery door) {
|
||||
Location l = door.getLocation();
|
||||
int rotation = door.getRotation();
|
||||
if (door instanceof Constructed && door.getDefinition().hasAction("close")) {
|
||||
Scenery o = ((Constructed) door).getReplaced();
|
||||
if (o != null) {
|
||||
l = o.getLocation();
|
||||
rotation = o.getRotation();
|
||||
}
|
||||
}
|
||||
if (door.getType() == 9) { // Diagonal doors
|
||||
switch (rotation) {
|
||||
case 0:
|
||||
case 2:
|
||||
if (entity.getLocation().getY() < l.getY() || entity.getLocation().getX() < l.getX()) {
|
||||
return l.transform(0, -1, 0);
|
||||
}
|
||||
return l.transform(0, 1, 0);
|
||||
case 1:
|
||||
case 3:
|
||||
if (entity.getLocation().getX() > l.getX() || entity.getLocation().getY() > l.getY()) {
|
||||
return l.transform(1, 0, 0);
|
||||
}
|
||||
return l.transform(-1, 0, 0);
|
||||
}
|
||||
}
|
||||
switch (rotation) {
|
||||
case 0:
|
||||
if (entity.getLocation().getX() < l.getX()) {
|
||||
if(Pathfinder.find(entity,l.transform(-1,0,0)).isMoveNear()){
|
||||
return l.transform(0,0,0);
|
||||
}
|
||||
return l.transform(-1, 0, 0);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (entity.getLocation().getY() > l.getY()) {
|
||||
if(Pathfinder.find(entity,l.transform(0,1,0)).isMoveNear()){
|
||||
return l.transform(0,0,0);
|
||||
}
|
||||
return l.transform(0, 1, 0);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (entity.getLocation().getX() > l.getX()) {
|
||||
if(Pathfinder.find(entity,l.transform(1,0,0)).isMoveNear()){
|
||||
return l.transform(0,0,0);
|
||||
}
|
||||
return l.transform(1, 0, 0);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (entity.getLocation().getY() < l.getY()) {
|
||||
if(Pathfinder.find(entity,l.transform(0,-1,0)).isMoveNear()){
|
||||
return l.transform(0,0,0);
|
||||
}
|
||||
return l.transform(0, -1, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the doors.
|
||||
*
|
||||
* @param object The door object.
|
||||
* @param second The second door object.
|
||||
* @param replaceId The replace id.
|
||||
* @param secondReplaceId The second replace id.
|
||||
* @param clip If clipping should be changed due to opening the door.
|
||||
* @param restoreTicks The amount of ticks before the door(s) should be
|
||||
* closed again.
|
||||
*/
|
||||
public static void open(Scenery object, Scenery second, int replaceId, int secondReplaceId, boolean clip, int restoreTicks, boolean fence) {
|
||||
object = object.getWrapper();
|
||||
int mod = object.getType() == 9 ? -1 : 1;
|
||||
int firstDir = (object.getRotation() + ((mod + 4) % 4)) % 4;
|
||||
Point p = getRotationPoint(object.getRotation());
|
||||
Location firstLoc = object.getLocation().transform((int) p.getX() * mod, (int) p.getY() * mod, 0);
|
||||
if (second == null) {
|
||||
if (replaceId == 4577) {
|
||||
replaceId = 4578;
|
||||
firstDir = 3;
|
||||
firstLoc = firstLoc.transform(0, 1, 0);
|
||||
}
|
||||
SceneryBuilder.replace(object, object.transform(replaceId, firstDir, firstLoc), restoreTicks, clip);
|
||||
return;
|
||||
}
|
||||
second = second.getWrapper();
|
||||
if (fence) {
|
||||
openFence(object, second, replaceId, secondReplaceId, clip, restoreTicks);
|
||||
return;
|
||||
}
|
||||
Direction offset = Direction.getDirection(second.getLocation().getX() - object.getLocation().getX(), second.getLocation().getY() - object.getLocation().getY());
|
||||
int secondDir = (second.getRotation() + mod) % 4;
|
||||
if (firstDir == 1 && offset == Direction.NORTH) {
|
||||
firstDir = 3;
|
||||
} else if (firstDir == 2 && offset == Direction.EAST) {
|
||||
firstDir = 0;
|
||||
} else if (firstDir == 3 && offset == Direction.SOUTH) {
|
||||
firstDir = 1;
|
||||
} else if (firstDir == 0 && offset == Direction.WEST) {
|
||||
firstDir = 2;
|
||||
}
|
||||
if (firstDir == secondDir) {
|
||||
secondDir = (secondDir + 2) % 4;
|
||||
}
|
||||
Location secondLoc = second.getLocation().transform((int) p.getX(), (int) p.getY(), 0);
|
||||
SceneryBuilder.replace(object, object.transform(replaceId, firstDir, firstLoc), restoreTicks, clip);
|
||||
SceneryBuilder.replace(second, second.transform(secondReplaceId, secondDir, secondLoc), restoreTicks, clip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the opening of a fence.
|
||||
*
|
||||
* @param object The fence object.
|
||||
* @param second The second fence object.
|
||||
* @param replaceId The replace id.
|
||||
* @param secondReplaceId The second replace id.
|
||||
* @param clip If clipping should be changed due to opening the door.
|
||||
* @param restoreTicks The amount of ticks before the door(s) should be
|
||||
* closed again.
|
||||
*/
|
||||
private static void openFence(Scenery object, Scenery second, int replaceId, int secondReplaceId, boolean clip, int restoreTicks) {
|
||||
Direction offset = Direction.getDirection(second.getLocation().getX() - object.getLocation().getX(), second.getLocation().getY() - object.getLocation().getY());
|
||||
int firstDir = (object.getRotation() + 3) % 4;
|
||||
Point p = getRotationPoint(object.getRotation());
|
||||
Location firstLoc = null;
|
||||
int secondDir = (second.getRotation() + 3) % 4;
|
||||
if (offset == Direction.WEST || offset == Direction.SOUTH) {
|
||||
firstLoc = second.getLocation().transform((int) p.getX(), (int) p.getY(), 0);
|
||||
int s = replaceId;
|
||||
replaceId = secondReplaceId;
|
||||
secondReplaceId = s;
|
||||
} else {
|
||||
firstLoc = object.getLocation().transform((int) p.getX(), (int) p.getY(), 0);
|
||||
}
|
||||
if (object.getRotation() == 3 || object.getRotation() == 2) {
|
||||
firstDir = (firstDir + 2) % 4;
|
||||
secondDir = (secondDir + 2) % 4;
|
||||
}
|
||||
Location secondLoc = firstLoc.transform((int) p.getX(), (int) p.getY(), 0);
|
||||
if ((object.getId() == 36917 || object.getId() == 36919)) {
|
||||
switch (object.getDirection()) {
|
||||
case SOUTH:
|
||||
SceneryBuilder.replace(object, object.transform(36919, firstDir, firstLoc), restoreTicks, true);
|
||||
SceneryBuilder.replace(second, second.transform(36917, secondDir, secondLoc), restoreTicks, true);
|
||||
break;
|
||||
case EAST:
|
||||
SceneryBuilder.replace(object, object.transform(36917, firstDir, firstLoc), restoreTicks, true);
|
||||
SceneryBuilder.replace(second, second.transform(36919, secondDir, secondLoc), restoreTicks, true);
|
||||
break;
|
||||
default:
|
||||
SceneryBuilder.replace(object, object.transform(36919, firstDir, firstLoc), restoreTicks, true);
|
||||
SceneryBuilder.replace(second, second.transform(36917, secondDir, secondLoc), restoreTicks, true);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
SceneryBuilder.replace(object, object.transform(replaceId, firstDir, firstLoc), restoreTicks, clip);
|
||||
SceneryBuilder.replace(second, second.transform(secondReplaceId, secondDir, secondLoc), restoreTicks, clip);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the opening of a fence.
|
||||
*
|
||||
* @param object The fence object.
|
||||
* @param replaceId The replace id.
|
||||
* @param secondReplaceId The second replace id.
|
||||
* closed again.
|
||||
*/
|
||||
public static boolean autowalkFence(final Entity entity, final Scenery object, final int replaceId, final int secondReplaceId) {
|
||||
final Scenery second = getSecondDoor(object, entity);
|
||||
if (object.getCharge() == IN_USE_CHARGE || second == null) {
|
||||
return false;
|
||||
}
|
||||
entity.lock(4);
|
||||
final Location loc = entity.getLocation();
|
||||
if(entity instanceof Player)
|
||||
{
|
||||
entity.asPlayer().logoutListeners.put("autowalk", player -> {
|
||||
player.setLocation(loc);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
object.setCharge(IN_USE_CHARGE);
|
||||
second.setCharge(IN_USE_CHARGE);
|
||||
GameWorld.getPulser().submit(new Pulse(1) {
|
||||
boolean opened = false;
|
||||
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
if (!opened) {
|
||||
openFence(object, second, replaceId, secondReplaceId, false, 2);
|
||||
Location l = getEndLocation(entity, object);
|
||||
entity.getWalkingQueue().reset();
|
||||
entity.getWalkingQueue().addPath(l.getX(), l.getY());
|
||||
opened = true;
|
||||
return false;
|
||||
}
|
||||
if(entity instanceof Player)
|
||||
{
|
||||
entity.asPlayer().logoutListeners.remove("autowalk");
|
||||
}
|
||||
object.setCharge(1000);
|
||||
if (second != null) {
|
||||
second.setCharge(1000);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the closing rotation point.
|
||||
*
|
||||
* @param object The object.
|
||||
* @return The point.
|
||||
*/
|
||||
private static Point getCloseRotation(Scenery object) {
|
||||
switch (object.getRotation()) {
|
||||
case 0:
|
||||
return new Point(0, 1);
|
||||
case 1:
|
||||
return new Point(1, 0);
|
||||
case 2:
|
||||
return new Point(0, -1);
|
||||
case 3:
|
||||
return new Point(-1, 0);
|
||||
}
|
||||
return new Point(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rotation point for the object.
|
||||
*
|
||||
* @return The rotation point.
|
||||
*/
|
||||
public static Point getRotationPoint(int rotation) {
|
||||
switch (rotation) {
|
||||
case 0:
|
||||
return new Point(-1, 0);
|
||||
case 1:
|
||||
return new Point(0, 1);
|
||||
case 2:
|
||||
return new Point(1, 0);
|
||||
case 3:
|
||||
return new Point(0, -1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the door next to this door.
|
||||
*
|
||||
* @param object The door.
|
||||
* @return The second door, if any, {@code null} if no second door existed.
|
||||
*/
|
||||
public static Scenery getSecondDoor(Scenery object, Entity entity) {
|
||||
Location l = object.getLocation();
|
||||
Player player = entity instanceof Player ? (Player) entity : null;
|
||||
Scenery o = null;
|
||||
if ((o = RegionManager.getObject(l.transform(-1, 0, 0))) != null && o.getName().equals(object.getName())) {
|
||||
return o;
|
||||
}
|
||||
if ((o = RegionManager.getObject(l.transform(1, 0, 0))) != null && o.getName().equals(object.getName())) {
|
||||
return o;
|
||||
}
|
||||
if ((o = RegionManager.getObject(l.transform(0, -1, 0))) != null && o.getName().equals(object.getName())) {
|
||||
return o;
|
||||
}
|
||||
if ((o = RegionManager.getObject(l.transform(0, 1, 0))) != null && o.getName().equals(object.getName())) {
|
||||
return o;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rotations to set the opened doors to.
|
||||
*
|
||||
* @param object The first door.
|
||||
* @param second The second door.
|
||||
* @param rp The rotation point.
|
||||
* @return An int-array, with index 0 being first door rotation and index 1
|
||||
* being second door rotation.
|
||||
*/
|
||||
public static int[] getRotation(Scenery object, Scenery second, Point rp) {
|
||||
if (second == null) {
|
||||
return new int[]{(object.getRotation() + 1) % 4};
|
||||
}
|
||||
int[] rotations = new int[]{3, 1};
|
||||
Location fl = object.getLocation();
|
||||
Location sl = second.getLocation();
|
||||
if (fl.getX() > sl.getX()) {
|
||||
rotations = new int[]{2, 0};
|
||||
}
|
||||
if (fl.getX() < sl.getX()) {
|
||||
rotations = new int[]{0, 2};
|
||||
}
|
||||
if (fl.getY() > sl.getY()) {
|
||||
rotations = new int[]{1, 3};
|
||||
}
|
||||
if (rp.getY() > 0 || rp.getX() > 0) {
|
||||
rotations = new int[]{rotations[1], rotations[0]};
|
||||
}
|
||||
return rotations;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package core.game.global.action;
|
||||
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.entity.player.info.login.PlayerParser;
|
||||
import core.game.node.entity.player.link.audio.Audio;
|
||||
import core.game.node.item.GroundItemManager;
|
||||
import core.game.node.item.Item;
|
||||
import core.game.node.entity.combat.graves.GraveController;
|
||||
import core.tools.SystemLogger;
|
||||
import core.game.system.config.ItemConfigParser;
|
||||
import core.game.world.GameWorld;
|
||||
|
||||
/**
|
||||
* Handles the dropping of an item.
|
||||
* @author Vexia
|
||||
*/
|
||||
public final class DropItemHandler {
|
||||
|
||||
/**
|
||||
* Handles the droping of an item.
|
||||
* @param player the player.
|
||||
* @param node the node.
|
||||
* @param option the option.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public static boolean handle(final Player player, Node node, String option) {
|
||||
Item item = (Item) node;
|
||||
if (item.getSlot() == -1) {
|
||||
player.getPacketDispatch().sendMessage("Invalid slot!");
|
||||
return false;
|
||||
}
|
||||
switch (option) {
|
||||
case "drop":
|
||||
case "destroy":
|
||||
case "dissolve":
|
||||
if (!player.getInterfaceManager().close()) {
|
||||
return true;
|
||||
}
|
||||
player.getDialogueInterpreter().close();
|
||||
player.getPulseManager().clear();
|
||||
if (option.equalsIgnoreCase("destroy") || option.equalsIgnoreCase("dissolve") || (boolean) item.getDefinition().getHandlers().getOrDefault(ItemConfigParser.DESTROY,false)) {
|
||||
player.getDialogueInterpreter().open(9878, item);
|
||||
return true;
|
||||
}
|
||||
if (GraveController.hasGraveAt(player.getLocation())) {
|
||||
player.sendMessage("You cannot drop items on top of graves!");
|
||||
return false;
|
||||
}
|
||||
if (player.getAttribute("equipLock:" + item.getId(), 0) > GameWorld.getTicks()) {
|
||||
SystemLogger.logAlert(DropItemHandler.class, player + ", tried to do the drop & equip dupe.");
|
||||
return true;
|
||||
}
|
||||
if (player.getInventory().replace(null, item.getSlot()) == item) {
|
||||
item = item.getDropItem();
|
||||
player.getAudioManager().send(new Audio(item.getId() == 995 ? 10 : 2739, 1, 0));//2739 ACTUAL DROP SOUND
|
||||
GroundItemManager.create(item, player.getLocation(), player);
|
||||
PlayerParser.save(player);
|
||||
}
|
||||
player.setAttribute("droppedItem:" + item.getId(), GameWorld.getTicks() + 2);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops an item.
|
||||
* @param player the player.
|
||||
* @param item the item.
|
||||
* @return
|
||||
*/
|
||||
public static boolean drop(Player player, Item item) {
|
||||
return handle(player, item, item.getDefinition().hasDestroyAction() ? "destroy" : "drop");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package core.game.global.action
|
||||
|
||||
import core.game.event.ItemEquipEvent
|
||||
import core.game.event.ItemUnequipEvent
|
||||
import core.game.container.impl.EquipmentContainer
|
||||
import content.global.handlers.item.equipment.brawling_gloves.BrawlingGlovesManager
|
||||
import content.global.skill.slayer.SlayerEquipmentFlags
|
||||
import core.game.node.Node
|
||||
import core.game.node.entity.combat.equipment.WeaponInterface
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.entity.player.link.audio.Audio
|
||||
import core.plugin.Plugin
|
||||
import core.game.interaction.InteractionListener
|
||||
import core.game.interaction.IntType
|
||||
import core.game.interaction.InteractionListeners
|
||||
import core.game.system.config.ItemConfigParser
|
||||
|
||||
/**
|
||||
* Represents the equipment equipping handler plugin.
|
||||
* @author Ceikry
|
||||
* @author Woah
|
||||
*/
|
||||
class EquipHandler : InteractionListener {
|
||||
|
||||
override fun defineListeners() {
|
||||
on(IntType.ITEM, "equip", "wield", "wear") { player, node ->
|
||||
handleEquip(player, node)
|
||||
return@on true
|
||||
}
|
||||
}
|
||||
|
||||
fun handleEquip(player: Player, node: Node) {
|
||||
val item = node.asItem()
|
||||
|
||||
if (item == null || player.inventory[item.slot] != item || item.name.toLowerCase().contains("goblin mail")) {
|
||||
return
|
||||
}
|
||||
|
||||
val equipStateListener = item.definition.getConfiguration<Plugin<Any>>("equipment", null)
|
||||
if (equipStateListener != null) {
|
||||
val bool = equipStateListener.fireEvent("equip", player, item)
|
||||
if (bool != true) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!InteractionListeners.run(node.id, player, node, true)) {
|
||||
return
|
||||
}
|
||||
|
||||
val lock = player.locks.equipmentLock
|
||||
if (lock != null && lock.isLocked) {
|
||||
if (lock.message != null) {
|
||||
player.packetDispatch.sendMessage(lock.message)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (player.equipment.add(item, item.slot, true, true)) {
|
||||
//check if a brawling glove is being equipped and register it
|
||||
if (item.id in 13845..13857) {
|
||||
player.debug("Registering gloves... ID: " + item.id)
|
||||
BrawlingGlovesManager.getInstance(player).registerGlove(item.id)
|
||||
}
|
||||
|
||||
player.dialogueInterpreter.close()
|
||||
|
||||
/* TODO: Send different equip sound based on what is being equip.*/
|
||||
player.audioManager.send(item.definition.getConfiguration(ItemConfigParser.EQUIP_AUDIO, 2244))
|
||||
if (player.properties.autocastSpell != null) {
|
||||
player.properties.autocastSpell = null
|
||||
val wif = player.getExtension<WeaponInterface>(WeaponInterface::class.java)
|
||||
wif.selectAutoSpell(-1, true)
|
||||
wif.openAutocastSelect()
|
||||
}
|
||||
|
||||
if (SlayerEquipmentFlags.isSlayerEq(item.id)) {
|
||||
SlayerEquipmentFlags.updateFlags(player)
|
||||
}
|
||||
|
||||
player.dispatch(ItemEquipEvent(item.id, item.slot))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Unequips an item.
|
||||
* @param player the player.
|
||||
* @param slot the slot.
|
||||
* @param itemId the item id.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun unequip(player: Player, slot: Int, itemId: Int) {
|
||||
if (slot < 0 || slot > 13) {
|
||||
return
|
||||
}
|
||||
val item = player.equipment[slot] ?: return
|
||||
val lock = player.locks.equipmentLock
|
||||
if (lock != null && lock.isLocked) {
|
||||
if (lock.message != null) {
|
||||
player.packetDispatch.sendMessage(lock.message)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (slot == EquipmentContainer.SLOT_WEAPON) {
|
||||
player.packetDispatch.sendString("", 92, 0)
|
||||
}
|
||||
val maximumAdd = player.inventory.getMaximumAdd(item)
|
||||
if (maximumAdd < item.amount) {
|
||||
player.packetDispatch.sendMessage("Not enough free space in your inventory.")
|
||||
return
|
||||
}
|
||||
val plugin = item.definition.getConfiguration<Plugin<Any>>("equipment", null)
|
||||
if (plugin != null) {
|
||||
if (!(plugin.fireEvent("unequip", player, item) as Boolean)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!InteractionListeners.run(itemId, player, item, false)) {
|
||||
return
|
||||
}
|
||||
if (player.equipment.remove(item)) {
|
||||
/* TODO: Send different unequip sound based on what is being unequipped.*/
|
||||
player.audioManager.send(Audio(2238, 10, 1))
|
||||
player.dialogueInterpreter.close()
|
||||
player.inventory.add(item)
|
||||
|
||||
player.dispatch(ItemUnequipEvent(itemId, slot))
|
||||
}
|
||||
|
||||
if (SlayerEquipmentFlags.isSlayerEq(item.id)) {
|
||||
SlayerEquipmentFlags.updateFlags(player)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package core.game.global.action;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
|
||||
interface LadderAchievementCheck {
|
||||
default void checkAchievement(Player player) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package core.game.global.action
|
||||
|
||||
import core.game.event.PickUpEvent
|
||||
import core.api.getItemName
|
||||
import content.data.GodType
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.entity.player.link.audio.Audio
|
||||
import content.global.skill.runecrafting.RunePouch
|
||||
import core.game.node.entity.player.info.LogType
|
||||
import core.game.node.item.GroundItem
|
||||
import core.game.node.item.GroundItemManager
|
||||
import core.game.node.item.Item
|
||||
import core.game.world.map.RegionManager
|
||||
import core.game.world.update.flag.context.Animation
|
||||
import core.game.node.entity.player.info.PlayerMonitor
|
||||
import core.tools.SystemLogger
|
||||
import core.game.system.config.GroundSpawnLoader
|
||||
import core.game.world.GameWorld
|
||||
|
||||
/**
|
||||
* A class used to handle the picking up of ground items.
|
||||
* @author 'Vexia
|
||||
*/
|
||||
object PickupHandler {
|
||||
/**
|
||||
* Method used to take a ground item.
|
||||
* @param player the player.
|
||||
* @param item the item.
|
||||
* @return `True` if taken.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun take(player: Player, item: GroundItem): Boolean {
|
||||
if (item.location == null) {
|
||||
player.packetDispatch.sendMessage("Invalid ground item!")
|
||||
return true
|
||||
}
|
||||
if (!GroundItemManager.getItems().contains(item)) {
|
||||
player.packetDispatch.sendMessage("Too late!")
|
||||
return true
|
||||
}
|
||||
if (player.getAttribute("droppedItem:" + item.id, 0) > GameWorld.ticks) { //Splinter
|
||||
SystemLogger.logAlert(this::class.java, "$player tried to do the drop & quick pick-up Ground Item dupe.")
|
||||
return true
|
||||
}
|
||||
if (item !is GroundSpawnLoader.GroundSpawn && item.isRemainPrivate && !item.droppedBy(player)) {
|
||||
player.sendMessage("You can't take that item!")
|
||||
return true
|
||||
}
|
||||
val add = Item(item.id, item.amount, item.charge)
|
||||
if (!player.inventory.hasSpaceFor(add)) {
|
||||
player.packetDispatch.sendMessage("You don't have enough inventory space to hold that item.")
|
||||
return true
|
||||
}
|
||||
if (!canTake(player, item, 0)) {
|
||||
return true
|
||||
}
|
||||
if (item.isActive && player.inventory.add(add)) {
|
||||
if (item.dropper is Player && item.dropper.details.uid != player.details.uid){
|
||||
PlayerMonitor.log(item.dropper, LogType.DROP_TRADE, "${getItemName(item.id)} x${item.amount} picked up by ${player.name}.")
|
||||
}
|
||||
if (!RegionManager.isTeleportPermitted(item.location)) {
|
||||
player.animate(Animation.create(535))
|
||||
}
|
||||
GroundItemManager.destroy(item)
|
||||
/* if (item.dropper?.isArtificial == true) {
|
||||
getItems(item.dropper)?.remove(item)
|
||||
}
|
||||
*/
|
||||
|
||||
player.audioManager.send(Audio(2582, 10, 1))
|
||||
player.dispatch(PickUpEvent(item.id))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the player can take an item.
|
||||
* @param player the player.
|
||||
* @param item the item.
|
||||
* @param type the type (1= ground, 2=telegrab)
|
||||
* @return `True` if so.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun canTake(player: Player, item: GroundItem, type: Int): Boolean {
|
||||
if (item.dropper != null && !item.droppedBy(player) && player.ironmanManager.checkRestriction()) {
|
||||
return false
|
||||
}
|
||||
if (item.id == 8858 || item.id == 8859) {
|
||||
player.dialogueInterpreter.sendDialogues(4300, core.game.dialogue.FacialExpression.FURIOUS, "Hey! You can't take that, it's guild property. Take one", "from the pile.")
|
||||
return false
|
||||
}
|
||||
if (GodType.forCape(item) != null) {
|
||||
if (GodType.hasAny(player)) {
|
||||
player.sendMessages("You may only possess one sacred cape at a time.", "The conflicting powers of the capes drive them apart.")
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (RunePouch.forItem(item) != null) {
|
||||
if (player.hasItem(item)) {
|
||||
player.sendMessage("A mystical force prevents you from picking up the pouch.")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return if (item.hasItemPlugin()) {
|
||||
item.plugin.canPickUp(player, item, type)
|
||||
} else true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package core.game.global.action;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.entity.player.link.diary.DiaryType;
|
||||
import core.game.world.map.Location;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
public enum SpecialLadders implements LadderAchievementCheck {
|
||||
HAM_STORAGE_UP(new Location(2567,5185,0), new Location(3166,9623,0)),
|
||||
GEM_MINE(new Location(2821,2996,0), new Location(2838,9387,0)),
|
||||
GEM_MINE_UP(new Location(2838,9388,0), new Location(2820, 2996, 0)),
|
||||
BEAR_CAGE_UP(Location.create(3230, 9904, 0),Location.create(3231, 3504, 0)),
|
||||
BEAR_CAGE_DOWN(Location.create(3230,3508,0),Location.create(3229, 9904, 0)),
|
||||
GLARIAL_EXIT(Location.create(2556,9844,0),Location.create(2557,3444,0)),
|
||||
SWENSEN_DOWN(Location.create(2644, 3657, 0), Location.create(2631, 10006, 0)),
|
||||
SWENSEN_UP(Location.create(2665, 10037, 0),Location.create(2649, 3661, 0)),
|
||||
FOG_ENTER(Location.create(3240,3575,0),Location.create(1675,5599,0)),
|
||||
FOG_LEAVE(Location.create(1673,5598,0),Location.create(3242, 3574, 0)),
|
||||
INTRO_LEAVE(Location.create(2522, 4999, 0),Location.create(3230, 3240, 0)),
|
||||
JATIZSO_MINE_UP(Location.create(2406,10188,0),Location.create(2397, 3811, 0)),
|
||||
JATIZSO_MINE_DOWN(Location.create(2397, 3812, 0), Location.create(2405, 10188, 0)),
|
||||
JATIZSO_SHOUT_TOWER_UP(Location.create(2373, 3800, 2),Location.create(2374, 3800, 0)),
|
||||
JATIZSO_SHOUT_TOWER_DOWN(Location.create(2373, 3800, 0),Location.create(2374, 3800, 2)),
|
||||
|
||||
ALKHARID_ZEKE_UP(Location.create(3284,3186,0), Location.create(3284,3190,1)),
|
||||
ALKHARID_ZEKE_DOWN(Location.create(3284,3190,1), Location.create(3284,3186,0)),
|
||||
ALKHARID_CRAFTING_UP(Location.create(3311,3187,0),Location.create(3314,3187,1)),
|
||||
ALKHARID_CRAFTING_DOWN(Location.create(3314,3187,1),Location.create(3310,3187,0)),
|
||||
ALKHARID_SOCRCERESS_UP(Location.create(3325,3142,0),Location.create(3325,3139,1)),
|
||||
ALKHARID_SOCRCERESS_DOWN(Location.create(3325,3139,1),Location.create(3325,3143,0)),
|
||||
|
||||
DRAYNOR_SEWER_SOUTHEAST_DOWN(new Location(3118, 3244, 0), new Location(3118, 9643, 0)),
|
||||
DRAYNOR_SEWER_SOUTHEAST_UP(new Location(3118, 9643, 0), new Location(3118, 3243, 0)),
|
||||
DRAYNOR_SEWER_NORTHWEST_DOWN(new Location(3084, 3272, 0), new Location(3085, 9672, 0)),
|
||||
DRAYNOR_SEWER_NORTHWEST_UP(new Location(3084, 9672, 0), new Location(3084, 3271, 0)),
|
||||
|
||||
PORT_SARIM_RAT_PITS_DOWN(new Location(3018,3232,0), new Location(2962,9650,0)) {
|
||||
@Override
|
||||
public void checkAchievement(Player player) {
|
||||
player.getAchievementDiaryManager().finishTask(player,DiaryType.FALADOR,1,11);
|
||||
}
|
||||
},
|
||||
PORT_SARIM_RAT_PITS_UP(new Location(2962,9651,0), new Location(3018,3231,0)),
|
||||
PHASMATYS_BAR_DOWN(new Location(3681,3498,0), new Location(3682,9961,0)),
|
||||
PHASMATYS_BAR_UP(new Location(3682,9962,0), new Location(3681,3497,0)),
|
||||
SEERS_VILLAGE_SPINNING_HOUSE_ROOFTOP_UP(new Location(2715,3472,1), new Location(2714,3472,3)) {
|
||||
@Override
|
||||
public void checkAchievement(Player player) {
|
||||
player.getAchievementDiaryManager().finishTask(player,DiaryType.SEERS_VILLAGE,1,3);
|
||||
}
|
||||
},
|
||||
SEERS_VILLAGE_SPINNING_HOUSE_ROOFTOP_DOWN(new Location(2715,3472,3), new Location(2714,3472,1));
|
||||
|
||||
private static HashMap<Location,Location> destinationMap = new HashMap<>();
|
||||
private static HashMap<Location,SpecialLadders> ladderMap = new HashMap<>();
|
||||
static {
|
||||
Arrays.stream(SpecialLadders.values()).forEach(entry -> {
|
||||
destinationMap.putIfAbsent(entry.ladderLoc,entry.destLoc);
|
||||
});
|
||||
Arrays.stream(SpecialLadders.values()).forEach(entry -> {
|
||||
ladderMap.putIfAbsent(entry.ladderLoc, entry);
|
||||
});
|
||||
}
|
||||
|
||||
private Location ladderLoc,destLoc;
|
||||
SpecialLadders(Location ladderLoc, Location destLoc){
|
||||
this.ladderLoc = ladderLoc;
|
||||
this.destLoc = destLoc;
|
||||
}
|
||||
|
||||
public static void add(Location from, Location to){
|
||||
destinationMap.put(from,to);
|
||||
}
|
||||
|
||||
public static Location getDestination(Location loc){
|
||||
return destinationMap.get(loc);
|
||||
}
|
||||
public static SpecialLadders getSpecialLadder(Location loc) {
|
||||
return ladderMap.get(loc);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package core.game.global.presets;
|
||||
|
||||
import core.game.node.item.Item;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Preset {
|
||||
|
||||
private ArrayList<Item> equipment;
|
||||
|
||||
private ArrayList<Item> inventory;
|
||||
|
||||
public Preset(ArrayList<Item> equipment, ArrayList<Item> inventory) {
|
||||
setEquipment(equipment);
|
||||
setInventory(inventory);
|
||||
}
|
||||
|
||||
public ArrayList<Item> getEquipment() {
|
||||
return equipment;
|
||||
}
|
||||
|
||||
public void setEquipment(ArrayList<Item> equipment) {
|
||||
this.equipment = equipment;
|
||||
}
|
||||
|
||||
public ArrayList<Item> getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
public void setInventory(ArrayList<Item> inventory) {
|
||||
this.inventory = inventory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package core.game.global.presets;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class PresetManager {
|
||||
|
||||
private Player player;
|
||||
|
||||
private final Map<String, Preset> current_presets;
|
||||
|
||||
public PresetManager() {
|
||||
current_presets = new HashMap<>();
|
||||
}
|
||||
|
||||
public PresetManager storeSet(String name_key) {
|
||||
final Preset set = current_presets.get(name_key);
|
||||
if (set != null) {
|
||||
player.sendMessage("You were unable to store the set " + name_key + " as it already exists.");
|
||||
}
|
||||
name_key = name_key.toLowerCase();
|
||||
ArrayList<Item> equipment = new ArrayList<Item>(Arrays.asList(player.getEquipment().getEvent().getItems())), inventory = new ArrayList<Item>(Arrays.asList(player.getInventory().getEvent().getItems()));
|
||||
current_presets.put(name_key, new Preset(equipment, inventory));
|
||||
return this;
|
||||
}
|
||||
|
||||
public void printAvailableSetups() {
|
||||
final int size = current_presets.size();
|
||||
player.sendMessage("You have used "+size+"/"+maxSize()+" available setups.");
|
||||
if (size > 0) {
|
||||
player.sendMessage("<col=ff0000>Your available setups are:");
|
||||
for (final String key : current_presets.keySet()) {
|
||||
player.sendMessage(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int maxSize() {
|
||||
return 6;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package core.game.global.report;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.integrations.discord.Discord;
|
||||
import core.game.system.command.CommandMapping;
|
||||
|
||||
/**
|
||||
* Represents an abuse report to file.
|
||||
* @author 'Vexia
|
||||
*/
|
||||
public final class AbuseReport {
|
||||
|
||||
/**
|
||||
* The name of the reporter.
|
||||
*/
|
||||
private final String reporter;
|
||||
|
||||
/**
|
||||
* The victim.
|
||||
*/
|
||||
private final String victim;
|
||||
|
||||
/**
|
||||
* The rule.
|
||||
*/
|
||||
private final Rule rule;
|
||||
|
||||
/**
|
||||
* The messages.
|
||||
*/
|
||||
private String messages;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code AbuseReport {@code Object}.
|
||||
* @param reporter the reporter.
|
||||
* @param victim the victim.
|
||||
* @param rule the rule broken.
|
||||
*/
|
||||
public AbuseReport(String reporter, String victim, Rule rule) {
|
||||
this.reporter = reporter;
|
||||
this.victim = victim;
|
||||
this.rule = rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the report and dispatches it to the database.
|
||||
* @param player the player.
|
||||
*/
|
||||
public void construct(Player player, boolean mute) {
|
||||
if (mute) {
|
||||
CommandMapping.INSTANCE.get("mute").attemptHandling(player, new String[] {"mute", victim, "48h"});
|
||||
}
|
||||
player.getPacketDispatch().sendMessage("Thank-you, your abuse report has been received.");
|
||||
Discord.INSTANCE.postPlayerAlert(victim, "Abuse Report - " + rule.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the reporter.
|
||||
* @return The reporter.
|
||||
*/
|
||||
public String getReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the victim.
|
||||
* @return The victim.
|
||||
*/
|
||||
public String getVictim() {
|
||||
return victim;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rule.
|
||||
* @return The rule.
|
||||
*/
|
||||
public Rule getRule() {
|
||||
return rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the messages.
|
||||
* @return The messages.
|
||||
*/
|
||||
public String getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the messages.
|
||||
* @param messages The messages to set.
|
||||
*/
|
||||
public void setMessages(String messages) {
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package core.game.global.report;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
|
||||
/**
|
||||
* Represents a rule.
|
||||
* @author 'Vexia
|
||||
*/
|
||||
public enum Rule {
|
||||
OFFENSIVE_LANGUAGE(0), ITEM_SCAMMING(1), PASSWORD_SCAMMING(2), BUG_ABUSE(3), KELDAGRIM_STAFF_IMPERSONATION(4), ACCCOUNT_SHARING(5), MACROING(6), MULTIPLE_LOGGING(7), ENCOURAGING_TO_BREAK_TULES(8), MISUSE_OF_CUSTOMER_SUPPORT(9), ADVERISTING(10), REAL_WORLD_ITEM_TRADING(11), ASKING_PERSONAL_DETAILS(12);
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Rule.java} {@code Object}.
|
||||
* @param rule the rule.
|
||||
*/
|
||||
Rule(int rule) {
|
||||
this.rule = rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rule id opcode.
|
||||
*/
|
||||
private int rule;
|
||||
|
||||
/**
|
||||
* Represents if the rule is relative to the sequences that has happend.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public boolean canRequest(Player target) {
|
||||
if (target == null) {
|
||||
return false;
|
||||
}
|
||||
if (target.getSavedData().getGlobalData().getChatPing() < System.currentTimeMillis()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rule.
|
||||
* @return the rule.
|
||||
*/
|
||||
public int getRule() {
|
||||
return rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rule.
|
||||
* @param id the id.
|
||||
* @return the rule.
|
||||
*/
|
||||
public static Rule forId(int id) {
|
||||
for (Rule rule : Rule.values()) {
|
||||
if (rule.getRule() == id) {
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
package core.game.interaction;
|
||||
|
||||
import core.game.global.action.DoorActionHandler;
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.Entity;
|
||||
import core.game.node.scenery.Scenery;
|
||||
import core.game.world.map.Direction;
|
||||
import core.game.world.map.Location;
|
||||
import core.game.world.map.RegionManager;
|
||||
|
||||
/**
|
||||
* Holds the destination flags for all types of nodes.
|
||||
* @author Emperor
|
||||
*/
|
||||
public class DestinationFlag {
|
||||
|
||||
/**
|
||||
* The Location destination flag.
|
||||
*/
|
||||
public static final DestinationFlag LOCATION = new DestinationFlag();
|
||||
|
||||
/**
|
||||
* The entity destination flag.
|
||||
*/
|
||||
public static final DestinationFlag ENTITY = new DestinationFlag() {
|
||||
|
||||
@Override
|
||||
public Location getDestination(Entity mover, Node n) {
|
||||
Location l = getClosestTo(mover, n, n.getLocation().transform(0, -1, 0));
|
||||
if (mover.size() > 1) {
|
||||
if (l.getX() < n.getLocation().getX()) {
|
||||
l = l.transform(-(mover.size() - 1), 0, 0);
|
||||
}
|
||||
if (l.getY() < n.getLocation().getY()) {
|
||||
l = l.transform(0, -(mover.size() - 1), 0);
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The following an entity destination flag.
|
||||
*/
|
||||
public static final DestinationFlag FOLLOW_ENTITY = new DestinationFlag() {
|
||||
|
||||
@Override
|
||||
public Location getDestination(Entity mover, Node n) {
|
||||
Direction dir = n.getDirection();
|
||||
Location l = n.getLocation().transform(-dir.getStepX(), -dir.getStepY(), 0);
|
||||
if (!checkTraversal(l, dir)) {
|
||||
l = getClosestTo(mover, n, l);
|
||||
}
|
||||
if (mover.size() > 1) {
|
||||
if (l.getX() < n.getLocation().getX()) {
|
||||
l = l.transform(-(mover.size() - 1), 0, 0);
|
||||
}
|
||||
if (l.getY() < n.getLocation().getY()) {
|
||||
l = l.transform(0, -(mover.size() - 1), 0);
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The entity destination flag.
|
||||
*/
|
||||
public static final DestinationFlag COMBAT = new DestinationFlag() {
|
||||
|
||||
@Override
|
||||
public Location getDestination(Entity mover, Node n) {
|
||||
return null;// MovementPulse.getClosest(mover, (Entity) n);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The item destination flag.
|
||||
*/
|
||||
public static final DestinationFlag ITEM = new DestinationFlag() {
|
||||
|
||||
@Override
|
||||
public Location getDestination(Entity mover, Node n) {
|
||||
if (!RegionManager.isTeleportPermitted(n.getLocation())) {
|
||||
return getClosestTo(mover, n, n.getLocation().transform(1, 0, 0));
|
||||
}
|
||||
return n.getLocation();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The object destination flag.
|
||||
*/
|
||||
public static final DestinationFlag OBJECT = new DestinationFlag() {
|
||||
|
||||
@Override
|
||||
public Location getDestination(Entity mover, Node n) {
|
||||
Scenery object = (Scenery) n;
|
||||
if (object.getType() < 4 || object.getType() == 9) {
|
||||
return DoorActionHandler.getDestination(mover, object);
|
||||
}
|
||||
if (object.getType() == 4 || object.getType() == 5) { // Wall
|
||||
// decoration
|
||||
return object.getLocation();
|
||||
}
|
||||
int sizeX = object.getDefinition().sizeX;
|
||||
int sizeY = object.getDefinition().sizeY;
|
||||
if (object.getRotation() % 2 != 0) {
|
||||
int switcher = sizeX;
|
||||
sizeX = sizeY;
|
||||
sizeY = switcher;
|
||||
}
|
||||
Direction dir = Direction.forWalkFlag(object.getDefinition().getWalkingFlag(), object.getRotation());
|
||||
if (dir != null) {
|
||||
return getDestination(mover, object, sizeX, sizeY, dir, 3);
|
||||
}
|
||||
// System.out.println(dir);
|
||||
return getDestination(mover, object, sizeX, sizeY, Direction.getLogicalDirection(object.getLocation(), mover.getLocation()), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the destination for the given object.
|
||||
* @param object The object.
|
||||
* @param dir The preferred direction from the object.
|
||||
* @return The teleporting destination.
|
||||
*/
|
||||
private Location getDestination(Entity mover, Scenery object, int sizeX, int sizeY, Direction dir, int count) {
|
||||
Location closest = null;
|
||||
double distance = 9999.9;
|
||||
Location loc = object.getLocation();
|
||||
for (int i = count; i < 4; i++) {
|
||||
if (dir.toInteger() % 2 != 0) {
|
||||
int x = dir.getStepX();
|
||||
if (x > 0) {
|
||||
x *= sizeX;
|
||||
}
|
||||
for (int y = 0; y < sizeY; y++) {
|
||||
Location l = loc.transform(x, y, 0);
|
||||
if (checkTraversal(l, dir)) {
|
||||
double dist = mover.getLocation().getDistance(l);
|
||||
if (dist < distance) {
|
||||
distance = dist;
|
||||
closest = l;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int y = dir.getStepY();
|
||||
if (y > 0) {
|
||||
y *= sizeY;
|
||||
}
|
||||
for (int x = 0; x < sizeX; x++) {
|
||||
Location l = loc.transform(x, y, 0);
|
||||
if (checkTraversal(l, dir)) {
|
||||
double dist = mover.getLocation().getDistance(l);
|
||||
if (dist < distance) {
|
||||
distance = dist;
|
||||
closest = l;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dir = Direction.get((dir.toInteger() + 1) % 4);
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTraversal(Location l, Direction dir) {
|
||||
return RegionManager.isTeleportPermitted(l) && dir.canMove(l);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs a new {@code DestinationFlag} {@code Object}.
|
||||
*/
|
||||
public DestinationFlag() {
|
||||
/*
|
||||
* empty.
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default destination location.
|
||||
* @param mover The moving entity.
|
||||
* @param node The node to move to.
|
||||
* @return The location to walk to.
|
||||
*/
|
||||
public Location getDestination(Entity mover, Node node) {
|
||||
return node.getLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if traversal is permitted.
|
||||
* @param l The location to check.
|
||||
* @param dir The direction to move.
|
||||
* @return {@code True}.
|
||||
*/
|
||||
public boolean checkTraversal(Location l, Direction dir) {
|
||||
return Direction.get((dir.toInteger() + 2) % 4).canMove(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the closest destination to the current destination, to reach the
|
||||
* node.
|
||||
* @param mover The moving entity.
|
||||
* @param node The node to move to.
|
||||
* @param suggestion The suggested destination location.
|
||||
* @return The destination location.
|
||||
*/
|
||||
public Location getClosestTo(Entity mover, Node node, Location suggestion) {
|
||||
Location nl = node.getLocation();
|
||||
int diffX = suggestion.getX() - nl.getX();
|
||||
int diffY = suggestion.getY() - nl.getY();
|
||||
Direction moveDir = Direction.NORTH;
|
||||
if (diffX < 0) {
|
||||
moveDir = Direction.EAST;
|
||||
} else if (diffX >= node.size()) {
|
||||
moveDir = Direction.WEST;
|
||||
} else if (diffY >= node.size()) {
|
||||
moveDir = Direction.SOUTH;
|
||||
}
|
||||
double distance = 9999.9;
|
||||
Location destination = suggestion;
|
||||
for (int c = 0; c < 4; c++) {
|
||||
for (int i = 0; i < node.size() + 1; i++) {
|
||||
for (int j = 0; j < (i == 0 ? 1 : 2); j++) {
|
||||
Direction current = Direction.get((moveDir.toInteger() + (j == 1 ? 3 : 1)) % 4);
|
||||
Location loc = suggestion.transform(current.getStepX() * i, current.getStepY() * i, 0);
|
||||
if (moveDir.toInteger() % 2 == 0) {
|
||||
if (loc.getX() < nl.getX() || loc.getX() > nl.getX() + node.size() - 1) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (loc.getY() < nl.getY() || loc.getY() > nl.getY() + node.size() - 1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (checkTraversal(loc, moveDir)) {
|
||||
double dist = mover.getLocation().getDistance(loc);
|
||||
if (dist < distance) {
|
||||
distance = dist;
|
||||
destination = loc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
moveDir = Direction.get((moveDir.toInteger() + 1) % 4);
|
||||
int offsetX = Math.abs(moveDir.getStepY() * (node.size() >> 1)); // Not
|
||||
// a
|
||||
// mixup
|
||||
// between
|
||||
// x
|
||||
// &
|
||||
// y!
|
||||
int offsetY = Math.abs(moveDir.getStepX() * (node.size() >> 1));
|
||||
if (moveDir.toInteger() < 2) {
|
||||
suggestion = node.getLocation().transform(-moveDir.getStepX() + offsetX, -moveDir.getStepY() + offsetY, 0);
|
||||
} else {
|
||||
suggestion = node.getLocation().transform(-moveDir.getStepX() * node.size() + offsetX, -moveDir.getStepY() * node.size() + offsetY, 0);
|
||||
}
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package core.game.interaction
|
||||
|
||||
enum class IntType {
|
||||
ITEM,
|
||||
SCENERY,
|
||||
NPC,
|
||||
GROUNDITEM,
|
||||
PLAYER
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
package core.game.interaction;
|
||||
|
||||
import core.game.event.InteractionEvent;
|
||||
import core.game.container.Container;
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.impl.PulseType;
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
import core.game.node.scenery.Scenery;
|
||||
import core.game.system.task.Pulse;
|
||||
import core.game.world.update.flag.player.FaceLocationFlag;
|
||||
import core.net.packet.PacketRepository;
|
||||
import core.net.packet.context.InteractionOptionContext;
|
||||
import core.net.packet.out.InteractionOption;
|
||||
import core.tools.SystemLogger;
|
||||
import core.game.world.GameWorld;
|
||||
|
||||
/**
|
||||
* Handles interaction between nodes.
|
||||
* @author Emperor
|
||||
*/
|
||||
public class Interaction {
|
||||
|
||||
/**
|
||||
* The current options.
|
||||
*/
|
||||
private Option[] options = new Option[8];
|
||||
|
||||
/**
|
||||
* The player.
|
||||
*/
|
||||
private final Node node;
|
||||
|
||||
/**
|
||||
* If the interaction option handlers have been initialized.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Interaction} {@code Object}.
|
||||
* @param node The node reference.
|
||||
*/
|
||||
public Interaction(Node node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the current option.
|
||||
* @param node The node.
|
||||
* @param index The index.
|
||||
* @param name The option name.
|
||||
*/
|
||||
public static void sendOption(Node node, int index, String name) {
|
||||
if (!(node instanceof Player)) {
|
||||
return;
|
||||
}
|
||||
PacketRepository.send(InteractionOption.class, new InteractionOptionContext((Player) node, index, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an interaction option being clicked.
|
||||
* @param player The player using the option.
|
||||
* @param option The option used.
|
||||
*/
|
||||
public void handle(final Player player, final Option option) {
|
||||
try {
|
||||
if (player.getLocks().isInteractionLocked() || option == null) {
|
||||
return;
|
||||
}
|
||||
player.debug("Received interaction request " + option.getName());
|
||||
boolean hasHandler = option.getHandler() != null;
|
||||
boolean walk = hasHandler && option.getHandler().isWalk();
|
||||
if (!walk && hasHandler && option.getHandler().isWalk(player, node)) {
|
||||
walk = true;
|
||||
}
|
||||
if (!hasHandler || walk) {
|
||||
handleWalkOption(player, option, PulseType.STANDARD);
|
||||
} else if (hasHandler) {
|
||||
player.debug("Option handler being used=" + option.getHandler().getClass().getSimpleName());
|
||||
handleDefaultOption(player, option, PulseType.STANDARD);
|
||||
} else {
|
||||
player.getPulseManager().runUnhandledAction(player, PulseType.STANDARD);
|
||||
}
|
||||
player.dispatch(new InteractionEvent(node, option.getName().toLowerCase()));
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
SystemLogger.logErr(this.getClass(), this.getClass().getName() + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an item option.
|
||||
* @param player The player.
|
||||
* @param option The option.
|
||||
* @param container The container the item is in.
|
||||
*/
|
||||
public void handleItemOption(final Player player, final Option option, final Container container) {
|
||||
if (player.getLocks().isInteractionLocked()) {
|
||||
return;
|
||||
}
|
||||
player.getPulseManager().clear(PulseType.STANDARD);
|
||||
GameWorld.getPulser().submit(new Pulse(1, player) {
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
try {
|
||||
if (player.getLocks().isInteractionLocked() || player.getZoneMonitor().interact(node, option)) {
|
||||
return true;
|
||||
}
|
||||
if(InteractionListeners.run(node.getId(), IntType.ITEM, option.getName(),player,node)){
|
||||
return true;
|
||||
}
|
||||
if (option.getHandler() == null || !option.getHandler().handle(player, node, option.getName().toLowerCase())) {
|
||||
player.getPacketDispatch().sendMessage("Nothing interesting happens.");
|
||||
}
|
||||
if (option.getHandler() != null) {
|
||||
player.debug("Using item handler " + option.getHandler().getClass().getSimpleName());
|
||||
}
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
SystemLogger.logErr(this.getClass(), this.getClass().getName() + e.getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an invalid interaction.
|
||||
* @param player The player.
|
||||
* @param node The node to interact with.
|
||||
*/
|
||||
public static void handleInvalidInteraction(final Player player, final Node node, final Option option) {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
if (node.getLocation() != null) {
|
||||
if (player.getLocks().isMovementLocked()) {
|
||||
return;
|
||||
}
|
||||
player.getPulseManager().run(new MovementPulse(player, node) {
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
try {
|
||||
player.faceLocation(FaceLocationFlag.getFaceLocation(player, node));
|
||||
if (player.getLocks().isInteractionLocked() || player.getZoneMonitor().interact(node, option)) {
|
||||
return true;
|
||||
}
|
||||
player.getPacketDispatch().sendMessage("Nothing interesting happens.");
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
SystemLogger.logErr(this.getClass(), this.getClass().getName() + e.getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}, PulseType.STANDARD);
|
||||
} else {
|
||||
player.getPulseManager().runUnhandledAction(player, PulseType.STANDARD);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an option requiring the player to walk to the node.
|
||||
* @param player The player.
|
||||
* @param option The option.
|
||||
* @param pulseType The pulse type.
|
||||
*/
|
||||
private void handleWalkOption(final Player player, final Option option, PulseType pulseType) {
|
||||
if (node.getLocation() == null) {
|
||||
player.getPulseManager().runUnhandledAction(player, pulseType);
|
||||
return;
|
||||
}
|
||||
if (player.getLocks().isMovementLocked()) {
|
||||
player.getPulseManager().clear(pulseType);
|
||||
return;
|
||||
}
|
||||
player.getPulseManager().run(new MovementPulse(player, node, option.getHandler()) {
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
try {
|
||||
player.faceLocation(FaceLocationFlag.getFaceLocation(player, node));
|
||||
if (player.getLocks().isInteractionLocked() || player.getZoneMonitor().interact(node, option)) {
|
||||
return true;
|
||||
}
|
||||
if (option == null || option.getHandler() == null || !option.getHandler().handle(player, node, option.getName().toLowerCase())) {
|
||||
player.getPacketDispatch().sendMessage("Nothing interesting happens.");
|
||||
}
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
SystemLogger.logErr(this.getClass(), this.getClass().getName() + e.getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}, pulseType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a default option.
|
||||
* @param player The player.
|
||||
* @param option The option.
|
||||
* @param pulseType The pulse type.
|
||||
*/
|
||||
private void handleDefaultOption(final Player player, final Option option, PulseType pulseType) {
|
||||
if (!option.getHandler().isDelayed(player)) {
|
||||
if (player.getZoneMonitor().interact(node, option)) {
|
||||
return;
|
||||
}
|
||||
player.getProperties().getCombatPulse().stop();
|
||||
if (!option.getHandler().handle(player, node, option.getName().toLowerCase())) {
|
||||
player.getPulseManager().runUnhandledAction(player, pulseType);
|
||||
}
|
||||
return;
|
||||
}
|
||||
player.getPulseManager().run(new Pulse(1, player, node) {
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
if (player.getLocks().isInteractionLocked() || player.getZoneMonitor().interact(node, option)) {
|
||||
return true;
|
||||
}
|
||||
if (!option.getHandler().handle(player, node, option.getName().toLowerCase())) {
|
||||
player.getPacketDispatch().sendMessage("Nothing interesting happens.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}, pulseType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the interaction options.
|
||||
* @param nodeId The node id.
|
||||
* @param names The option names.
|
||||
*/
|
||||
public void init(int nodeId, String... names) {
|
||||
options = new Option[names.length];
|
||||
for (int i = 0; i < options.length; i++) {
|
||||
String name = names[i];
|
||||
if (name != null && !name.equals("null")) {
|
||||
set(new Option(name, i).setHandler(Option.defaultHandler(node, nodeId, name)));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default option handlers (if not yet initialized).
|
||||
*/
|
||||
public void setDefault() {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
if (node instanceof Player) {
|
||||
for (int i = 0; i < options.length; i++) {
|
||||
remove(i);
|
||||
}
|
||||
set(Option._P_FOLLOW);
|
||||
set(Option._P_TRADE);
|
||||
set(Option._P_ASSIST);
|
||||
} else if (node instanceof NPC) {
|
||||
NPC npc = (NPC) node;
|
||||
init(npc.getId(), npc.getDefinition().getOptions());
|
||||
} else if (node instanceof Scenery) {
|
||||
Scenery object = (Scenery) node;
|
||||
init(object.getId(), object.getDefinition().getOptions());
|
||||
} else if (node instanceof Item) {
|
||||
Item item = (Item) node;
|
||||
if (item.getLocation() != null) {
|
||||
init(item.getId(), item.getDefinition().getGroundOptions());
|
||||
} else {
|
||||
init(item.getId(), item.getDefinition().getInventoryOptions());
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Unsupported node type - " + node);
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new option.
|
||||
* @param option The option.
|
||||
*/
|
||||
public void set(Option option) {
|
||||
options[option.getIndex()] = option;
|
||||
sendOption(node, option.getIndex(), option.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an option.
|
||||
* @return {@code True} if the option got removed, {@code false} if the
|
||||
* option wasn't set.
|
||||
*/
|
||||
public boolean remove(Option option) {
|
||||
if (options[option.getIndex()] == option) {
|
||||
remove(option.getIndex());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an option.
|
||||
* @param index The index.
|
||||
*/
|
||||
public void remove(int index) {
|
||||
if (options[index] == null) {
|
||||
return;
|
||||
}
|
||||
options[index] = null;
|
||||
sendOption(node, index, "null");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the option on the requested slot.
|
||||
* @param index The slot index.
|
||||
* @return The option on that slot, or {@code null} if there was no option.
|
||||
*/
|
||||
public Option get(int index) {
|
||||
return options[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the options.
|
||||
* @return The options.
|
||||
*/
|
||||
public Option[] getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the initialized value.
|
||||
* @return The initialized.
|
||||
*/
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initialized value.
|
||||
* @param initialized The initialized to set.
|
||||
*/
|
||||
public void setInitialized(boolean initialized) {
|
||||
this.initialized = initialized;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package core.game.interaction
|
||||
|
||||
import core.api.ContentInterface
|
||||
import core.game.node.Node
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.world.map.Location
|
||||
import content.global.handlers.item.SpadeDigListener
|
||||
|
||||
interface InteractionListener : ContentInterface{
|
||||
val ITEM
|
||||
get() = IntType.ITEM
|
||||
val GROUNDITEM
|
||||
get() = IntType.GROUNDITEM
|
||||
val NPC
|
||||
get() = IntType.NPC
|
||||
val SCENERY
|
||||
get() = IntType.SCENERY
|
||||
|
||||
fun on(id: Int, type: IntType, vararg option: String, handler: (player: Player, node: Node) -> Boolean){
|
||||
InteractionListeners.add(id, type.ordinal, option, handler)
|
||||
}
|
||||
fun on(ids: IntArray, type: IntType, vararg option: String, handler: (player: Player, node: Node) -> Boolean){
|
||||
InteractionListeners.add(ids, type.ordinal, option, handler)
|
||||
}
|
||||
fun on(option: String, type: IntType, handler: (player: Player, node: Node) -> Boolean){
|
||||
InteractionListeners.add(option, type.ordinal, handler)
|
||||
}
|
||||
fun on(type: IntType, vararg option: String, handler: (player: Player, node: Node) -> Boolean){
|
||||
InteractionListeners.add(option, type.ordinal, handler)
|
||||
}
|
||||
fun onUseWith(type: IntType, used: Int, vararg with: Int, handler: (player: Player, used: Node, with: Node) -> Boolean){
|
||||
InteractionListeners.add(type.ordinal, used, with, handler)
|
||||
}
|
||||
fun onUseWith(type: IntType, used: IntArray, vararg with: Int, handler: (player: Player, used: Node, with: Node) -> Boolean){
|
||||
InteractionListeners.add(type.ordinal, used, with, handler)
|
||||
}
|
||||
fun onUseAnyWith(type: IntType, vararg with: Int, handler: (player: Player, used: Node, with: Node) -> Boolean) {
|
||||
InteractionListeners.add(type.ordinal, with, handler)
|
||||
}
|
||||
fun onUseWithPlayer(vararg used: Int, handler: (player: Player, used: Node, with: Node) -> Boolean) {
|
||||
InteractionListeners.add(IntType.PLAYER.ordinal, used, handler)
|
||||
}
|
||||
// Note: wildcard listeners incur overhead on every use-with interaction, only use them as a space-time tradeoff when something
|
||||
// is actually supposed to have a response to every item used with it (e.g. imp boxes, certain quest npcs)
|
||||
fun onUseWithWildcard(type: IntType, predicate: (used: Int, with: Int) -> Boolean, handler: (player: Player, used: Node, with: Node) -> Boolean) {
|
||||
InteractionListeners.addWildcard(type.ordinal, predicate, handler)
|
||||
}
|
||||
fun onEquip(id: Int, handler: (player: Player, node: Node) -> Boolean){
|
||||
InteractionListeners.addEquip(id, handler)
|
||||
}
|
||||
fun onUnequip(id:Int, handler: (player: Player, node: Node) -> Boolean){
|
||||
InteractionListeners.addUnequip(id, handler)
|
||||
}
|
||||
|
||||
fun onEquip(ids: IntArray, handler: (player: Player, node: Node) -> Boolean){
|
||||
ids.forEach { id -> InteractionListeners.addEquip(id, handler) }
|
||||
}
|
||||
fun onUnequip(ids:IntArray, handler: (player: Player, node: Node) -> Boolean){
|
||||
ids.forEach{ id -> InteractionListeners.addUnequip(id, handler) }
|
||||
}
|
||||
|
||||
fun defineDestinationOverrides(){}
|
||||
|
||||
fun setDest(type: IntType, id: Int, handler: (Entity, Node) -> Location){
|
||||
InteractionListeners.addDestOverride(type.ordinal, id, handler)
|
||||
}
|
||||
fun setDest(type: IntType, vararg options: String, handler: (Entity, Node) -> Location){
|
||||
InteractionListeners.addDestOverrides(type.ordinal, options, handler)
|
||||
}
|
||||
|
||||
fun setDest(type: IntType, ids: IntArray, vararg options: String, handler: (Entity, Node) -> Location){
|
||||
InteractionListeners.addDestOverrides(type.ordinal, ids, options, handler)
|
||||
}
|
||||
|
||||
fun onDig(location: Location,method: (player: Player) -> Unit){
|
||||
SpadeDigListener.registerListener(location, method)
|
||||
}
|
||||
|
||||
fun flagInstant() {
|
||||
val name = this::class.java.name
|
||||
InteractionListeners.instantClasses.add(name)
|
||||
}
|
||||
|
||||
fun defineListeners()
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package core.game.interaction
|
||||
|
||||
import core.game.event.InteractionEvent
|
||||
import core.game.event.UseWithEvent
|
||||
import core.game.node.Node
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.world.map.Location
|
||||
|
||||
object InteractionListeners {
|
||||
private val listeners = HashMap<String,(Player, Node) -> Boolean>(1000)
|
||||
private val useWithListeners = HashMap<String,(Player,Node,Node) -> Boolean>(1000)
|
||||
private val useAnyWithListeners = HashMap<String, (Player, Node, Node) -> Boolean>(10)
|
||||
private val useWithWildcardListeners = HashMap<Int, ArrayList<Pair<(Int, Int) -> Boolean, (Player, Node, Node) -> Boolean>>>(10)
|
||||
private val destinationOverrides = HashMap<String,(Entity, Node) -> Location>(100)
|
||||
private val equipListeners = HashMap<String,(Player,Node) -> Boolean>(10)
|
||||
val instantClasses = HashSet<String>()
|
||||
|
||||
@JvmStatic
|
||||
fun add(id: Int, type: Int, option: Array<out String>, method: (Player,Node) -> Boolean){
|
||||
for(opt in option) {
|
||||
val key = "$id:$type:${opt.toLowerCase()}"
|
||||
if (!validate(key)) {
|
||||
throw IllegalStateException("$opt for $id with type ${IntType.values()[type].name} already defined! Existing use: [${listeners[key]!!::class.toString()}]")
|
||||
}
|
||||
listeners[key] = method
|
||||
}
|
||||
}
|
||||
|
||||
private fun validate(key: String): Boolean {
|
||||
return !listeners.containsKey(key) && !useWithListeners.containsKey(key)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun add(ids: IntArray, type: Int, option: Array<out String>, method: (Player,Node) -> Boolean){
|
||||
for(id in ids){
|
||||
add(id,type,option,method)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun add(option: String,type: Int, method: (Player,Node) -> Boolean){
|
||||
val key = "$type:${option.toLowerCase()}"
|
||||
if (!validate(key)) {
|
||||
throw IllegalStateException("Catchall listener for $option on type ${IntType.values()[type].name} already in use: ${listeners[key]!!::class}")
|
||||
}
|
||||
listeners[key] = method
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun add(options: Array<out String>,type: Int,method: (Player, Node) -> Boolean){
|
||||
for(opt in options){
|
||||
add(opt.toLowerCase(),type,method)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun add(used: Int, with: Int, type: Int, method: (Player,Node,Node) -> Boolean){
|
||||
val key = "$used:$with:$type"
|
||||
val altKey = "$with:$used:$type"
|
||||
if (!validate(key) || !validate(altKey)) {
|
||||
throw IllegalStateException("Usewith using $used with $with for type ${IntType.values()[type]} already in use: [${(useWithListeners[key] ?: useWithListeners[altKey])!!::class}")
|
||||
}
|
||||
useWithListeners[key] = method
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun add(type: Int, used: Int, with: IntArray, method: (Player, Node, Node) -> Boolean){
|
||||
for(id in with){
|
||||
add(used = used, with = id, type = type, method = method)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addWildcard(type: Int, predicate: (used: Int, with: Int) -> Boolean, handler: (player: Player, used: Node, with: Node) -> Boolean) {
|
||||
if(!useWithWildcardListeners.containsKey(type)) {
|
||||
useWithWildcardListeners.put(type, ArrayList())
|
||||
}
|
||||
useWithWildcardListeners[type]!!.add(Pair(predicate, handler))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addEquip(id: Int,method: (Player, Node) -> Boolean){
|
||||
equipListeners["equip:$id"] = method
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addUnequip(id: Int, method: (Player,Node) -> Boolean){
|
||||
equipListeners["unequip:$id"] = method
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getEquip(id: Int): ((Player,Node) -> Boolean)? {
|
||||
return equipListeners["equip:$id"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getUnequip(id: Int): ((Player,Node) -> Boolean)? {
|
||||
return equipListeners["unequip:$id"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun get(used: Int, with: Int, type: Int): ((Player,Node,Node) -> Boolean)? {
|
||||
val method = useWithListeners["$used:$with:$type"] ?: useAnyWithListeners["$with:$type"]
|
||||
if(method != null) {
|
||||
return method
|
||||
}
|
||||
val handlers = useWithWildcardListeners[type] ?: return null
|
||||
for(pair in handlers) {
|
||||
if(pair.first(used, with)) {
|
||||
return pair.second
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun get(id: Int, type: Int, option: String): ((Player,Node) -> Boolean)?{
|
||||
return listeners["$id:$type:${option.toLowerCase()}"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun get(option: String,type: Int): ((Player,Node) -> Boolean)?{
|
||||
return listeners["$type:${option.toLowerCase()}"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addDestOverride(type: Int, id: Int, method: (Entity,Node) -> Location){
|
||||
destinationOverrides["$type:$id"] = method
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addDestOverrides(type: Int,options: Array<out String>, method: (Entity,Node) -> Location){
|
||||
for(opt in options){
|
||||
destinationOverrides["$type:${opt.toLowerCase()}"] = method
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addDestOverrides(type: Int, ids: IntArray,options: Array<out String>, method: (Entity,Node) -> Location){
|
||||
for(id in ids){
|
||||
for(opt in options){
|
||||
destinationOverrides["$type:$id:${opt.toLowerCase()}"] = method
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getOverride(type: Int, id:Int, option: String): ((Entity, Node) -> Location)?{
|
||||
return destinationOverrides["$type:$id:${option.toLowerCase()}"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getOverride(type: Int,id: Int): ((Entity,Node) -> Location)?{
|
||||
return destinationOverrides["$type:$id"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getOverride(type: Int,option: String): ((Entity,Node) -> Location)?{
|
||||
return destinationOverrides["$type:$option"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun run(id: Int, player: Player, node: Node, isEquip: Boolean): Boolean{
|
||||
player.dispatch(InteractionEvent(node, if(isEquip) "equip" else "unequip"))
|
||||
if(isEquip){
|
||||
return equipListeners["equip:$id"]?.invoke(player,node) ?: true
|
||||
} else {
|
||||
return equipListeners["unequip:$id"]?.invoke(player,node) ?: true
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun run(used: Node, with: Node, type: IntType, player: Player): Boolean{
|
||||
val flag = when(type){
|
||||
IntType.NPC, IntType.PLAYER -> DestinationFlag.ENTITY
|
||||
IntType.SCENERY -> DestinationFlag.OBJECT
|
||||
IntType.GROUNDITEM -> DestinationFlag.ITEM
|
||||
else -> DestinationFlag.OBJECT
|
||||
}
|
||||
|
||||
if(player.locks.isInteractionLocked) return false
|
||||
|
||||
var flipped = false
|
||||
|
||||
val method = if (with is Player) get(-1, used.id, 4) ?: return false
|
||||
else get(used.id,with.id,type.ordinal) ?:
|
||||
if (type == IntType.ITEM)
|
||||
get(with.id,used.id,type.ordinal).also { flipped = true } ?: return false
|
||||
else return false
|
||||
|
||||
val destOverride = if(flipped) {
|
||||
getOverride(type.ordinal, used.id, "use") ?: getOverride(type.ordinal, with.id) ?: getOverride(type.ordinal, "use")
|
||||
} else {
|
||||
getOverride(type.ordinal, with.id, "use") ?: getOverride(type.ordinal, used.id) ?: getOverride(type.ordinal, "use")
|
||||
}
|
||||
|
||||
|
||||
if(type != IntType.ITEM && !isUseWithInstant(method)) {
|
||||
if(player.locks.isMovementLocked) return false
|
||||
player.pulseManager.run(object : MovementPulse(player, with, flag, destOverride) {
|
||||
override fun pulse(): Boolean {
|
||||
if (player.zoneMonitor.useWith(used.asItem(), with)) {
|
||||
return true
|
||||
}
|
||||
player.faceLocation(with.location)
|
||||
if(flipped) player.dispatch(UseWithEvent(with.id, used.id))
|
||||
else player.dispatch(UseWithEvent(used.id, with.id))
|
||||
if(flipped) method.invoke(player,with,used)
|
||||
else method.invoke(player,used,with)
|
||||
return true
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if(flipped) player.dispatch(UseWithEvent(with.id, used.id))
|
||||
else player.dispatch(UseWithEvent(used.id, with.id))
|
||||
if(flipped) method.invoke(player,with,used)
|
||||
else method.invoke(player,used,with)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun run(id: Int, type: IntType, option: String, player: Player, node: Node): Boolean{
|
||||
val flag = when(type){
|
||||
IntType.PLAYER -> DestinationFlag.ENTITY
|
||||
IntType.GROUNDITEM -> DestinationFlag.ITEM
|
||||
IntType.NPC -> DestinationFlag.ENTITY
|
||||
IntType.SCENERY -> null
|
||||
else -> DestinationFlag.OBJECT
|
||||
}
|
||||
|
||||
if(player.locks.isInteractionLocked) return false
|
||||
|
||||
val method = get(id,type.ordinal,option) ?: get(option,type.ordinal) ?: return false
|
||||
val destOverride = getOverride(type.ordinal, id, option) ?: getOverride(type.ordinal,node.id) ?: getOverride(type.ordinal,option.toLowerCase())
|
||||
|
||||
player.setAttribute("interact:option", option)
|
||||
|
||||
if(option.toLowerCase() == "attack") //Attack needs special handling >.>
|
||||
{
|
||||
player.dispatch(InteractionEvent(node, option.toLowerCase()))
|
||||
method.invoke(player, node)
|
||||
return true
|
||||
}
|
||||
|
||||
if(type != IntType.ITEM && !isInstant(method)) {
|
||||
if(player.locks.isMovementLocked) return false
|
||||
player.pulseManager.run(object : MovementPulse(player, node, flag, destOverride) {
|
||||
override fun pulse(): Boolean {
|
||||
if(player.zoneMonitor.interact(node, Option(option, 0))) return true
|
||||
player.faceLocation(node.location)
|
||||
player.dispatch(InteractionEvent(node, option.toLowerCase()))
|
||||
method.invoke(player,node)
|
||||
return true
|
||||
}
|
||||
})
|
||||
} else {
|
||||
method.invoke(player,node)
|
||||
player.dispatch(InteractionEvent(node, option.toLowerCase()))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun add(type: Int, used: IntArray, with: IntArray, handler: (Player, Node, Node) -> Boolean) {
|
||||
for(u in used){
|
||||
for (w in with){
|
||||
useWithListeners["$u:$w:$type"] = handler
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun add(type: Int, with: IntArray, handler: (Player, Node, Node) -> Boolean) {
|
||||
for (w in with) {
|
||||
useAnyWithListeners["$w:$type"] = handler
|
||||
}
|
||||
}
|
||||
|
||||
fun isInstant(handler: (Player, Node) -> Boolean): Boolean {
|
||||
val className = handler.javaClass.name.substringBefore("$")
|
||||
return instantClasses.contains(className)
|
||||
}
|
||||
|
||||
fun isUseWithInstant(handler: (player: Player, used: Node, with: Node) -> Boolean): Boolean {
|
||||
val className = handler.javaClass.name.substringBefore("$")
|
||||
return instantClasses.contains(className)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package core.game.interaction
|
||||
|
||||
import core.api.ContentInterface
|
||||
import core.game.component.Component
|
||||
import core.game.node.entity.player.Player
|
||||
|
||||
/**
|
||||
* An interface for writing content that allows the class to handle game interface interactions
|
||||
*
|
||||
* Interactions should be defined in the required [defineInterfaceListeners] method.
|
||||
*/
|
||||
interface InterfaceListener : ContentInterface {
|
||||
fun defineInterfaceListeners()
|
||||
|
||||
fun on(componentID: Int, buttonID: Int, handler: (player: Player, component: Component, opcode: Int, buttonID: Int, slot: Int, itemID: Int) -> Boolean){
|
||||
InterfaceListeners.add(componentID, buttonID, handler)
|
||||
}
|
||||
fun on(componentID: Int, handler: (player: Player, component: Component, opcode: Int, buttonID: Int, slot: Int, itemID: Int) -> Boolean){
|
||||
InterfaceListeners.add(componentID, handler)
|
||||
}
|
||||
fun onOpen(componentID: Int,handler: (player: Player, component: Component) -> Boolean){
|
||||
InterfaceListeners.addOpenListener(componentID, handler)
|
||||
}
|
||||
fun onClose(componentID: Int,handler: (player: Player, component: Component) -> Boolean){
|
||||
InterfaceListeners.addCloseListener(componentID, handler)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package core.game.interaction
|
||||
|
||||
import core.game.component.Component
|
||||
import core.game.node.entity.player.Player
|
||||
|
||||
object InterfaceListeners {
|
||||
val buttonListeners = HashMap<String,(Player, Component, Int, Int, Int, Int) -> Boolean>(1000)
|
||||
val openListeners = HashMap<String,(Player,Component) -> Boolean>(100)
|
||||
|
||||
@JvmStatic
|
||||
fun add(componentID: Int, buttonID: Int, handler: (Player,Component,Int,Int,Int,Int) -> Boolean){
|
||||
buttonListeners["$componentID:$buttonID"] = handler
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun add(componentID: Int, handler: (Player, Component, Int, Int, Int, Int) -> Boolean){
|
||||
buttonListeners["$componentID"] = handler
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addOpenListener(componentID: Int, handler: (Player,Component) -> Boolean){
|
||||
openListeners["$componentID"] = handler
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addCloseListener(componentID: Int, handler: (Player, Component) -> Boolean){
|
||||
openListeners["close:$componentID"] = handler
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun get(componentID: Int, buttonID: Int): ((Player,Component,Int,Int,Int,Int) -> Boolean)?{
|
||||
return buttonListeners["$componentID:$buttonID"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun get(componentID: Int): ((Player,Component,Int,Int,Int,Int) -> Boolean)?{
|
||||
return buttonListeners["$componentID"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getOpenListener(componentID: Int): ((Player,Component) -> Boolean)?{
|
||||
return openListeners["$componentID"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getCloseListener(componentID: Int): ((Player,Component) -> Boolean)?{
|
||||
return openListeners["close:$componentID"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun runOpen(player: Player,component: Component): Boolean{
|
||||
val method = getOpenListener(component.id) ?: return false
|
||||
return method.invoke(player,component)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun runClose(player: Player,component: Component): Boolean{
|
||||
val method = getCloseListener(component.id) ?: return true
|
||||
return method.invoke(player,component)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun run(player: Player, component: Component, opcode: Int, buttonID: Int, slot: Int, itemID: Int): Boolean{
|
||||
val method = get(component.id,buttonID) ?: get(component.id) ?: return false
|
||||
return method.invoke(player,component,opcode,buttonID,slot,itemID)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package core.game.interaction
|
||||
|
||||
import core.api.StartupListener
|
||||
|
||||
interface Listener : StartupListener {
|
||||
override fun startup() {
|
||||
defineListeners()
|
||||
}
|
||||
|
||||
fun defineListeners()
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
package core.game.interaction;
|
||||
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.Entity;
|
||||
import core.game.node.entity.impl.WalkingQueue;
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.system.task.Pulse;
|
||||
import core.game.world.map.Direction;
|
||||
import core.game.world.map.Location;
|
||||
import core.game.world.map.Point;
|
||||
import core.game.world.map.path.Path;
|
||||
import core.game.world.map.path.Pathfinder;
|
||||
import core.net.packet.PacketRepository;
|
||||
import core.net.packet.context.PlayerContext;
|
||||
import core.net.packet.out.ClearMinimapFlag;
|
||||
import kotlin.jvm.functions.Function2;
|
||||
import core.tools.SystemLogger;
|
||||
|
||||
import java.util.Deque;
|
||||
|
||||
/**
|
||||
* Handles a movement task.
|
||||
*
|
||||
* @author Emperor
|
||||
*/
|
||||
public abstract class MovementPulse extends Pulse {
|
||||
|
||||
/**
|
||||
* The moving entity.
|
||||
*/
|
||||
protected Entity mover;
|
||||
|
||||
/**
|
||||
* The destination node.
|
||||
*/
|
||||
protected Node destination;
|
||||
|
||||
/**
|
||||
* The destination's last location.
|
||||
*/
|
||||
private Location last;
|
||||
|
||||
/**
|
||||
* The pathfinder.
|
||||
*/
|
||||
private Pathfinder pathfinder;
|
||||
|
||||
/**
|
||||
* If running should be forced.
|
||||
*/
|
||||
private boolean forceRun;
|
||||
|
||||
/**
|
||||
* The option handler.
|
||||
*/
|
||||
private OptionHandler optionHandler;
|
||||
|
||||
/**
|
||||
* The use with handler.
|
||||
*/
|
||||
private UseWithHandler useHandler;
|
||||
|
||||
/**
|
||||
* The destination flag.
|
||||
*/
|
||||
private DestinationFlag destinationFlag;
|
||||
|
||||
/**
|
||||
* The location to interact from.
|
||||
*/
|
||||
private Location interactLocation;
|
||||
|
||||
/**
|
||||
* If the path couldn't be fully found.
|
||||
*/
|
||||
private boolean near;
|
||||
|
||||
private Function2<Entity,Node,Location> overrideMethod;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code MovementPulse} {@code Object}.
|
||||
*
|
||||
* @param mover The moving entity.
|
||||
* @param destination The destination node.
|
||||
*/
|
||||
public MovementPulse(Entity mover, Node destination) {
|
||||
this(mover, destination, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code MovementPulse} {@code Object}.
|
||||
*
|
||||
* @param mover The moving entity.
|
||||
* @param destination The destination node.
|
||||
* @param forceRun If the entity is forced to run.
|
||||
*/
|
||||
public MovementPulse(Entity mover, Node destination, boolean forceRun) {
|
||||
this(mover, destination, null, forceRun);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code MovementPulse} {@code Object}.
|
||||
*
|
||||
* @param mover The moving entity.
|
||||
* @param destination The destination node.
|
||||
* @param pathfinder The pathfinder to use.
|
||||
*/
|
||||
public MovementPulse(Entity mover, Node destination, Pathfinder pathfinder) {
|
||||
this(mover, destination, pathfinder, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code MovementPulse} {@code Object}.
|
||||
*
|
||||
* @param mover The moving entity.
|
||||
* @param destination The destination node.
|
||||
* @param optionHandler The option handler used.
|
||||
*/
|
||||
public MovementPulse(Entity mover, Node destination, OptionHandler optionHandler) {
|
||||
this(mover, destination, null, false);
|
||||
this.optionHandler = optionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code MovementPulse} {@code Object}.
|
||||
*
|
||||
* @param mover The moving entity.
|
||||
* @param destination The destination node.
|
||||
* @param useHandler The use with handler used.
|
||||
*/
|
||||
public MovementPulse(Entity mover, Node destination, UseWithHandler useHandler) {
|
||||
this(mover, destination, null, false);
|
||||
this.useHandler = useHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code MovementPulse} {@code Object}.
|
||||
*
|
||||
* @param mover The moving entity.
|
||||
* @param destination The destination node.
|
||||
* @param destinationFlag The destination flag.
|
||||
*/
|
||||
public MovementPulse(Entity mover, Node destination, DestinationFlag destinationFlag) {
|
||||
this(mover, destination, null, false);
|
||||
this.destinationFlag = destinationFlag;
|
||||
}
|
||||
|
||||
public MovementPulse(Entity mover, Node destination, DestinationFlag destinationFlag, Function2<Entity,Node,Location> method){
|
||||
this(mover,destination,null,false);
|
||||
this.destinationFlag = destinationFlag;
|
||||
this.overrideMethod = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code MovementPulse} {@code Object}.
|
||||
*
|
||||
* @param mover The moving entity.
|
||||
* @param destination The destination node.
|
||||
* @param pathfinder The pathfinder to use.
|
||||
* @param forceRun If the entity is forced to run.
|
||||
*/
|
||||
public MovementPulse(Entity mover, Node destination, Pathfinder pathfinder, boolean forceRun) {
|
||||
super(1, mover, destination);
|
||||
this.mover = mover;
|
||||
this.destination = destination;
|
||||
if (pathfinder == null) {
|
||||
if (mover instanceof Player) {
|
||||
this.pathfinder = Pathfinder.SMART;
|
||||
} else {
|
||||
this.pathfinder = Pathfinder.DUMB;
|
||||
}
|
||||
} else {
|
||||
this.pathfinder = pathfinder;
|
||||
}
|
||||
this.forceRun = forceRun;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean update() {
|
||||
mover.face(null);
|
||||
if (mover == null || destination == null || mover.getViewport().getRegion() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasInactiveNode() || !mover.getViewport().getRegion().isActive()) {
|
||||
stop();
|
||||
return true;
|
||||
}
|
||||
if (!isRunning()) {
|
||||
return true;
|
||||
}
|
||||
findPath();
|
||||
Location ml = mover.getLocation();
|
||||
// Allow being within 1 square of moving entities to interact with them.
|
||||
int radius = destination instanceof Entity && ((Entity)destination).getWalkingQueue().hasPath() ? 1 : 0;
|
||||
if (Math.max(Math.abs(ml.getX() - interactLocation.getX()), Math.abs(ml.getY() - interactLocation.getY())) <= radius) {
|
||||
try {
|
||||
if (near || pulse()) {
|
||||
if (mover instanceof Player) {
|
||||
if (near) {
|
||||
((Player) mover).getPacketDispatch().sendMessage("I can't reach that.");
|
||||
}
|
||||
PacketRepository.send(ClearMinimapFlag.class, new PlayerContext((Player) mover));
|
||||
}
|
||||
stop();
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
stop();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
if (destination instanceof Entity) {
|
||||
mover.face(null);
|
||||
}
|
||||
last = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a path to the destination, if necessary.
|
||||
*/
|
||||
public void findPath() {
|
||||
if (mover instanceof NPC && mover.asNpc().isNeverWalks()) {
|
||||
return;
|
||||
}
|
||||
if(destination.getLocation() == null){
|
||||
SystemLogger.logAlert(this.getClass(), destination.getId() + " < ID");
|
||||
SystemLogger.logAlert(this.getClass(), destination.getName() + " < NAME");
|
||||
SystemLogger.logAlert(this.getClass(), "ASDAD");
|
||||
return;
|
||||
}
|
||||
boolean inside = isInsideEntity(mover.getLocation());
|
||||
/* This appears to have been a premature optimization that lead to a bug that would cause both entities
|
||||
to completely stop moving mid-combat/mid-follow-dance/etc
|
||||
if (last != null && last.equals(destination.getLocation()) && !inside) {
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
Location loc = null;
|
||||
|
||||
if (destinationFlag != null && overrideMethod == null) {
|
||||
loc = destinationFlag.getDestination(mover, destination);
|
||||
}
|
||||
else if(overrideMethod != null){
|
||||
loc = overrideMethod.invoke(mover,destination);
|
||||
if(loc == destination.getLocation()) loc = destinationFlag.getDestination(mover,destination);
|
||||
}
|
||||
|
||||
if (loc == null) {
|
||||
if (optionHandler != null) {
|
||||
loc = optionHandler.getDestination(mover, destination);
|
||||
}
|
||||
else if (useHandler != null) {
|
||||
loc = useHandler.getDestination((Player) mover, destination);
|
||||
}
|
||||
else if (inside) {
|
||||
loc = findBorderLocation();
|
||||
}
|
||||
}
|
||||
|
||||
if (destination == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location ml = mover.getLocation();
|
||||
Location dl = destination.getLocation();
|
||||
// Lead the target if they're walking/running, unless they're already within interaction range
|
||||
if(loc != null && destination instanceof Entity && Math.max(Math.abs(ml.getX() - dl.getX()), Math.abs(ml.getY() - dl.getY())) > 1) {
|
||||
WalkingQueue wq = ((Entity)destination).getWalkingQueue();
|
||||
if(wq.hasPath()) {
|
||||
Point[] points = wq.getQueue().toArray(new Point[0]);
|
||||
if(points.length > 0) {
|
||||
Point p = points[0];
|
||||
for(int i=0; i<points.length; i++) {
|
||||
// Target the farthest point along target's planned movement that's within 1 tick's running,
|
||||
// this ensures the player will run to catch up to the target if able.
|
||||
if(Math.max(Math.abs(ml.getX() - points[i].getX()), Math.abs(ml.getY() - points[i].getY())) <= 2) {
|
||||
p = points[i];
|
||||
}
|
||||
}
|
||||
loc.setX(p.getX());
|
||||
loc.setY(p.getY());
|
||||
}
|
||||
}
|
||||
}
|
||||
Path path = Pathfinder.find(mover, loc != null ? loc : destination, true, pathfinder);
|
||||
near = !path.isSuccessful() || path.isMoveNear();
|
||||
interactLocation = mover.getLocation();
|
||||
boolean canMove = true;
|
||||
if (destination instanceof NPC || destination instanceof Player) {
|
||||
Entity e = (Entity) destination;
|
||||
Location l = e.getLocation();
|
||||
Deque<Point> npcPath = e.getWalkingQueue().getQueue();
|
||||
if (e.getWalkingQueue().hasPath() && e.getProperties().getCombatPulse().isRunning() && e.getProperties().getCombatPulse().getVictim() == mover)
|
||||
canMove = false;
|
||||
if (!canMove) { //If we normally shouldn't move, but the NPC's pathfinding is not letting them move, then move.
|
||||
if (npcPath.size() == 1) {
|
||||
Point pathElement = npcPath.peek();
|
||||
if (pathElement.getX() == l.getX() && pathElement.getY() == l.getY())
|
||||
canMove = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!path.getPoints().isEmpty() && canMove) {
|
||||
Point point = path.getPoints().getLast();
|
||||
interactLocation = Location.create(point.getX(), point.getY(), mover.getLocation().getZ());
|
||||
if (forceRun) {
|
||||
mover.getWalkingQueue().reset(forceRun);
|
||||
} else {
|
||||
mover.getWalkingQueue().reset();
|
||||
}
|
||||
int size = path.getPoints().toArray().length;
|
||||
Deque points = path.getPoints();
|
||||
for (int i = 0; i < size; i++) {
|
||||
point = path.getPoints().pop();
|
||||
mover.getWalkingQueue().addPath(point.getX(), point.getY());
|
||||
if (destination instanceof Entity) {
|
||||
mover.face((Entity) destination);
|
||||
} else {
|
||||
mover.face(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
last = destination.getLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the closest location next to the node.
|
||||
*
|
||||
* @return The location to walk to.
|
||||
*/
|
||||
private Location findBorderLocation() {
|
||||
int size = destination.size();
|
||||
Location centerDest = destination.getLocation().transform(size >> 1, size >> 1, 0);
|
||||
Location center = mover.getLocation().transform(mover.size() >> 1, mover.size() >> 1, 0);
|
||||
Direction direction = Direction.getLogicalDirection(centerDest, center);
|
||||
Location delta = Location.getDelta(destination.getLocation(), mover.getLocation());
|
||||
main:
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int amount = 0;
|
||||
switch (direction) {
|
||||
case NORTH:
|
||||
amount = size - delta.getY();
|
||||
break;
|
||||
case EAST:
|
||||
amount = size - delta.getX();
|
||||
break;
|
||||
case SOUTH:
|
||||
amount = mover.size() + delta.getY();
|
||||
break;
|
||||
case WEST:
|
||||
amount = mover.size() + delta.getX();
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
for (int j = 0; j < amount; j++) {
|
||||
for (int s = 0; s < mover.size(); s++) {
|
||||
switch (direction) {
|
||||
case NORTH:
|
||||
if (!direction.canMove(mover.getLocation().transform(s, j + mover.size(), 0))) {
|
||||
direction = Direction.get((direction.toInteger() + 1) & 3);
|
||||
continue main;
|
||||
}
|
||||
break;
|
||||
case EAST:
|
||||
if (!direction.canMove(mover.getLocation().transform(j + mover.size(), s, 0))) {
|
||||
direction = Direction.get((direction.toInteger() + 1) & 3);
|
||||
continue main;
|
||||
}
|
||||
break;
|
||||
case SOUTH:
|
||||
if (!direction.canMove(mover.getLocation().transform(s, -(j + 1), 0))) {
|
||||
direction = Direction.get((direction.toInteger() + 1) & 3);
|
||||
continue main;
|
||||
}
|
||||
break;
|
||||
case WEST:
|
||||
if (!direction.canMove(mover.getLocation().transform(-(j + 1), s, 0))) {
|
||||
direction = Direction.get((direction.toInteger() + 1) & 3);
|
||||
continue main;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Location location = mover.getLocation().transform(direction, amount);
|
||||
return location;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the mover is standing on an invalid position.
|
||||
*
|
||||
* @param l The location.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
private boolean isInsideEntity(Location l) {
|
||||
if (!(destination instanceof Entity)) {
|
||||
return false;
|
||||
}
|
||||
if (((Entity) destination).getWalkingQueue().isMoving()) {
|
||||
return false;
|
||||
}
|
||||
Location loc = destination.getLocation();
|
||||
int size = destination.size();
|
||||
return Pathfinder.isStandingIn(l.getX(), l.getY(), mover.size(), mover.size(), loc.getX(), loc.getY(), size, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the forceRun.
|
||||
*
|
||||
* @return The forceRun.
|
||||
*/
|
||||
public boolean isForceRun() {
|
||||
return forceRun;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the forceRun.
|
||||
*
|
||||
* @param forceRun The forceRun to set.
|
||||
*/
|
||||
public void setForceRun(boolean forceRun) {
|
||||
this.forceRun = forceRun;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current destination.
|
||||
*
|
||||
* @param destination The destination.
|
||||
*/
|
||||
public void setDestination(Node destination) {
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the last location.
|
||||
*
|
||||
* @param last The last location.
|
||||
*/
|
||||
public void setLast(Location last) {
|
||||
this.last = last;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user