Rewrote all the logic, encoding, etc for player/npc rendering masks (Animations, force movements, etc.)

Groundwork for modular 578 support has now been laid (at least in regards to render masks)
Introduced an improved and more accurate API for using force movement (the old API is still around, all of its methods marked as deprecated)
Converted a couple example cases to using the new force movement API just as a proof of concept
Added a method to tick/time conversion methods to convert between client cycles and ticks
Added all authentically-named buffer read/write methods to the buffer class
This commit is contained in:
Ceikry 2023-05-25 14:58:40 +00:00 committed by Ryan
parent 9329d9d008
commit 2cb8d82648
58 changed files with 963 additions and 1259 deletions

View file

@ -9,7 +9,7 @@ import core.game.node.item.Item
import core.game.world.map.Location
import core.game.world.map.RegionManager
import core.game.world.update.flag.context.ChatMessage
import core.game.world.update.flag.player.ChatFlag
import core.game.world.update.flag.*
import core.integrations.discord.Discord
import org.json.simple.JSONArray
import org.json.simple.JSONObject
@ -132,14 +132,14 @@ class DoublingMoney : Script() {
val message = arrayOf("Doubling Money", "Doubling Money", "Doubling Money!", "Doubling moneyy").random()
val messageEffect = arrayOf(0, 256).random()
val ctx = ChatMessage(bot, message, messageEffect, message.length)
bot.updateMasks.register(ChatFlag(ctx))
bot.updateMasks.register(EntityFlag.Chat, ctx)
sleepTime = 8
}
Effort.VERY_HIGH -> {
val message = arrayOf("Doubling money!", "Doubling money").random()
val messageEffect = arrayOf(771, 2818, 2562, 768, 512, 2304, 2560, 769, 1792).random()
val ctx = ChatMessage(bot, message, messageEffect, message.length)
bot.updateMasks.register(ChatFlag(ctx))
bot.updateMasks.register(EntityFlag.Chat, ctx)
sleepTime = 9
}
}
@ -198,7 +198,7 @@ class DoublingMoney : Script() {
if (botTradeModule.getInterface() == TradeModule.ACCEPT_INTERFACE && coinsFromBot > 0 && effort == Effort.VERY_HIGH) {
val message = "Payed ${(if (coinsFromBot < 1000) "${coinsFromBot}gp" else "${coinsFromBot / 1000}k")}"
val ctx = ChatMessage(bot, message, 512, message.length)
bot.updateMasks.register(ChatFlag(ctx))
bot.updateMasks.register(EntityFlag.Chat, ctx)
sleepTime = 7
}
@ -325,7 +325,7 @@ class DoublingMoney : Script() {
if (effort == Effort.VERY_HIGH) {
val message = "Received ${(if (coins.amount < 1000) "${coins.amount}gp" else "${coins.amount / 1000}k")}"
val ctx = ChatMessage(bot, message, 256, message.length)
bot.updateMasks.register(ChatFlag(ctx))
bot.updateMasks.register(EntityFlag.Chat, ctx)
}
}

View file

@ -9,7 +9,6 @@ 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 core.game.world.update.flag.player.FaceLocationFlag
import core.plugin.Initializable
import core.plugin.Plugin
@ -136,7 +135,7 @@ class BasaltRockShortcut : AgilityShortcut {
player.sendMessage(noJump)
} else {
player.lock(3)
player.faceLocation(FaceLocationFlag.getFaceLocation(player, Location.create(2518, 3611, 0)))
player.faceLocation(Location.create(2518, 3611, 0))
AgilityHandler.forceWalk(player, -1, Location.create(2516, 3611, 0), Location.create(2518, 3611, 0), Animation.create(769), 20, 0.0, null, 1)
}
return true
@ -147,7 +146,7 @@ class BasaltRockShortcut : AgilityShortcut {
player.sendMessage(noJump)
} else {
player.lock(3)
player.faceLocation(FaceLocationFlag.getFaceLocation(player, Location.create(2516, 3611, 0)))
player.faceLocation(Location.create(2516, 3611, 0))
AgilityHandler.forceWalk(player, -1, Location.create(2518, 3611, 0), Location.create(2516, 3611, 0), Animation.create(769), 20, 0.0, null, 1)
}
return true
@ -199,4 +198,4 @@ class BasaltRockShortcut : AgilityShortcut {
}
})
}
}
}

View file

@ -12,7 +12,6 @@ import core.game.node.scenery.SceneryBuilder;
import core.game.world.GameWorld;
import core.game.world.map.RegionManager;
import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.player.FaceLocationFlag;
import core.tools.RandomFunction;
/**
@ -129,7 +128,7 @@ public final class FireMakingPulse extends SkillPulse<Item> {
SceneryBuilder.add(object, fire.getLife(), getAsh(player, fire, object));
GroundItemManager.destroy(groundItem);
player.moveStep();
player.faceLocation(FaceLocationFlag.getFaceLocation(player, object));
player.faceLocation(object.getFaceLocation(player.getLocation()));
player.getSkills().addExperience(Skills.FIREMAKING,fire.getXp());
int playerRegion = player.getViewport().getRegion().getId();
@ -191,4 +190,4 @@ public final class FireMakingPulse extends SkillPulse<Item> {
}
return false;
}
}
}

View file

@ -4,7 +4,6 @@ import core.cache.def.impl.NPCDefinition;
import core.game.interaction.OptionHandler;
import core.game.node.Node;
import core.game.node.entity.player.Player;
import core.game.world.update.flag.player.FaceLocationFlag;
import core.plugin.Initializable;
import core.plugin.Plugin;
@ -39,7 +38,7 @@ public final class FamiliarNPCOptionPlugin extends OptionHandler {
}
switch (option) {
case "pick-up":
player.faceLocation(FaceLocationFlag.getFaceLocation(player, familiar));
player.faceLocation(familiar.getFaceLocation(player.getLocation()));
player.getFamiliarManager().pickup();
break;
case "interact-with":

View file

@ -12,7 +12,6 @@ import core.game.system.task.Pulse;
import core.game.world.GameWorld;
import core.game.world.map.RegionManager;
import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.player.FaceLocationFlag;
import core.plugin.Initializable;
import core.plugin.Plugin;
import core.plugin.ClassScanner;
@ -153,7 +152,7 @@ public class ForgeRegentNPC extends Familiar {
familiar.moveStep();
GroundItemManager.destroy(ground);
player.getSkills().addExperience(Skills.FIREMAKING, log.getXp() + 10);
familiar.faceLocation(FaceLocationFlag.getFaceLocation(familiar, object));
familiar.faceLocation(object.getFaceLocation(familiar.getLocation()));
SceneryBuilder.add(object, log.getLife(), FireMakingPulse.getAsh(player, log, object));
if (player.getViewport().getRegion().getId() == 10806) {
player.getAchievementDiaryManager().finishTask(player, DiaryType.SEERS_VILLAGE, 1, 9);

View file

@ -21,7 +21,6 @@ import core.game.world.GameWorld;
import core.game.world.map.RegionManager;
import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.context.Graphics;
import core.game.world.update.flag.player.FaceLocationFlag;
import core.plugin.Plugin;
import core.plugin.ClassScanner;
@ -132,7 +131,7 @@ public class PyreLordNPC extends Familiar {
familiar.moveStep();
GroundItemManager.destroy(ground);
player.getSkills().addExperience(Skills.FIREMAKING, log.getXp() + 10);
familiar.faceLocation(FaceLocationFlag.getFaceLocation(familiar, object));
familiar.faceLocation(object.getFaceLocation(familiar.getLocation()));
SceneryBuilder.add(object, log.getLife(), FireMakingPulse.getAsh(player, log, object));
if (player.getViewport().getRegion().getId() == 10806) {
player.getAchievementDiaryManager().finishTask(player, DiaryType.SEERS_VILLAGE, 1, 9);

View file

@ -38,6 +38,7 @@ import core.tools.RandomFunction;
import static core.api.ContentAPIKt.animateScenery;
import static core.api.ContentAPIKt.submitWorldPulse;
import static core.api.ContentAPIKt.animationDuration;
import static core.api.ContentAPIKt.forceMove;
/**
* Handles the puro puro activity.
@ -161,13 +162,7 @@ public final class PuroPuroPlugin extends MapZone implements Plugin<Object> {
player.sendMessage("You use your strength to push through the wheat. It's hard work though.");
}
player.setAttribute("cantMove", true);
GameWorld.getPulser().submit(new Pulse(1) {
@Override
public boolean pulse() {
ForceMovement.run(player, player.getLocation(), dest, Animation.create(6594), Animation.create(6594), Direction.getLogicalDirection(player.getLocation(), object.getLocation()), 3, 3);
return true;
}
});
forceMove(player, player.getLocation(), dest, 0, 265, null, 6595, null);
}
/**

View file

@ -85,22 +85,7 @@ class TreeGnomeVillageListeners : InteractionListener {
return@on true
}
on(looseRailing, IntType.SCENERY, "squeeze-through"){ player, _ ->
if(player.location != location(2516,3161,0)) {
squeezeThrough(player)
} else {
player.pulseManager.run(object : Pulse(0) {
var count = 0
override fun pulse(): Boolean {
when (count) {
0 -> ForceMovement.run(player,player.location,player.location.transform(Direction.WEST,1))
2 -> squeezeThrough(player)
3 -> return true
}
count++
return false
}
})
}
squeezeThrough(player)
return@on true
}
on(crumbledWall, IntType.SCENERY, "climb-over"){ player, _ ->
@ -132,71 +117,55 @@ class TreeGnomeVillageListeners : InteractionListener {
return@on true
}
}
}
fun squeezeThrough(player: Player){
val squeezeAnim = Animation.create(3844)
if(player.location.y >= 3161) {
AgilityHandler.forceWalk(
player,
-1,
player.location,
player.location.transform(Direction.SOUTH, 1),
squeezeAnim,
5,
0.0,
null
)
} else {
AgilityHandler.forceWalk(
player,
-1,
player.location,
player.location.transform(Direction.NORTH, 1),
squeezeAnim,
5,
0.0,
null
)
fun squeezeThrough(player: Player){
val squeezeAnim = Animation.create(3844)
var dest = if (player.location.y >= 3161)
player.location.transform(Direction.SOUTH, 1)
else
player.location.transform(Direction.NORTH, 1)
forceMove(player, player.location, dest, 0, 80, anim = 3844)
}
}
private class ClimbWall : DialogueFile() {
val climbAnimation = Animation(839)
val wallLoc = Location(2509,3253,0)
override fun handle(componentID: Int, buttonID: Int) {
if(questStage(player!!, TreeGnomeVillage.questName) > 30){
val northSouth = if (player!!.location.y <= wallLoc.y) Direction.NORTH else Direction.SOUTH
when(stage){
0 -> sendDialogue(player!!,"The wall has been reduced to rubble. It should be possible to climb over the remains").also{ stage++ }
1 -> AgilityHandler.forceWalk(player!!, -1, player!!.location, player!!.location.transform(northSouth, 2), climbAnimation, 20, 0.0, null).also {
end()
if(northSouth == Direction.SOUTH) return
val lowerGuard: NPC = RegionManager.getNpc(player!!.location, NPCs.KHAZARD_COMMANDER_478, 6) ?: return
GameWorld.Pulser.submit(object : Pulse(0) {
var count = 0
override fun pulse(): Boolean {
when (count) {
0 -> {
player!!.lock(4)
lowerGuard.sendChat("What? How did you manage to get in here.")
}
2 -> {
player!!.sendChat("I've come for the orb.")
}
3 -> {
lowerGuard.sendChat("I'll never let you take it.")
lowerGuard.attack(player)
player!!.unlock()
return true
private class ClimbWall : DialogueFile() {
val climbAnimation = Animation(839)
val wallLoc = Location(2509,3253,0)
override fun handle(componentID: Int, buttonID: Int) {
if(questStage(player!!, TreeGnomeVillage.questName) > 30){
val northSouth = if (player!!.location.y <= wallLoc.y) Direction.NORTH else Direction.SOUTH
when(stage){
0 -> sendDialogue(player!!,"The wall has been reduced to rubble. It should be possible to climb over the remains").also{ stage++ }
1 -> AgilityHandler.forceWalk(player!!, -1, player!!.location, player!!.location.transform(northSouth, 2), climbAnimation, 20, 0.0, null).also {
end()
if(northSouth == Direction.SOUTH) return
val lowerGuard: NPC = RegionManager.getNpc(player!!.location, NPCs.KHAZARD_COMMANDER_478, 6) ?: return
GameWorld.Pulser.submit(object : Pulse(0) {
var count = 0
override fun pulse(): Boolean {
when (count) {
0 -> {
player!!.lock(4)
lowerGuard.sendChat("What? How did you manage to get in here.")
}
2 -> {
player!!.sendChat("I've come for the orb.")
}
3 -> {
lowerGuard.sendChat("I'll never let you take it.")
lowerGuard.attack(player)
player!!.unlock()
return true
}
}
count++
return false
}
count++
return false
}
})
})
}
}
}
}
}
}
}

View file

@ -9,7 +9,6 @@ import core.game.system.task.Pulse;
import core.game.world.GameWorld;
import core.game.world.update.flag.context.Animation;
import core.plugin.Initializable;
import core.game.world.update.flag.player.FaceLocationFlag;
/**
* Represents the dialogue plugin used for mithril seeds.
@ -64,7 +63,7 @@ public final class MithrilSeedsDialogue extends DialoguePlugin {
switch (buttonId) {
case 1: // First option
player.lock(2);
player.faceLocation(FaceLocationFlag.getFaceLocation(player, flower));
player.faceLocation(flower.getFaceLocation(player.getLocation()));
player.animate(ANIMATION);
GameWorld.getPulser().submit(new Pulse(2, player, flower) {
@Override

View file

@ -11,7 +11,6 @@ import core.game.system.task.Pulse;
import core.game.world.GameWorld;
import core.game.world.map.RegionManager;
import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.player.FaceLocationFlag;
import core.plugin.Initializable;
import core.plugin.Plugin;
import core.tools.RandomFunction;
@ -67,7 +66,7 @@ public final class MithrilSeedsPlugin extends OptionHandler {
player.getPulseManager().run(new Pulse(1, player) {
@Override
public boolean pulse() {
player.faceLocation(FaceLocationFlag.getFaceLocation(player, object));
player.faceLocation(object.getFaceLocation(player.getLocation()));
player.getDialogueInterpreter().open(1 << 16 | 1, object);
return true;
}

View file

@ -29,7 +29,6 @@ import core.game.world.map.Location;
import core.game.world.map.RegionManager;
import core.game.world.map.zone.ZoneBorders;
import core.game.world.map.zone.ZoneRestriction;
import core.game.world.update.flag.player.AppearanceFlag;
import core.tools.RandomFunction;
/**
@ -140,7 +139,7 @@ public final class TzhaarFightPitsPlugin extends ActivityPlugin {
lastVictor.getAchievementDiaryManager().finishTask(lastVictor, DiaryType.KARAMJA, 2, 0);
addTokkul(lastVictor);
lastVictor.getAppearance().setSkullIcon(SKULL_ID);
lastVictor.getUpdateMasks().register(new AppearanceFlag(lastVictor));
lastVictor.updateAppearance();
lastVictor.getPacketDispatch().sendString("Current Champion: " + getChampionName(), INTERFACE_ID, 0);
resetDamagePulse(lastVictor);
}
@ -158,7 +157,7 @@ public final class TzhaarFightPitsPlugin extends ActivityPlugin {
player.getInteraction().remove(Option._P_ATTACK);
if (player.getAppearance().getSkullIcon() == SKULL_ID) {
player.getAppearance().setSkullIcon(-1);
player.getUpdateMasks().register(new AppearanceFlag(player));
player.updateAppearance();
}
}
}
@ -190,7 +189,7 @@ public final class TzhaarFightPitsPlugin extends ActivityPlugin {
}
if (lastVictor.getAppearance().getSkullIcon() == SKULL_ID) {
player.getAppearance().setSkullIcon(-1);
player.getUpdateMasks().register(new AppearanceFlag(player));
player.updateAppearance();
}
}
@ -238,7 +237,7 @@ public final class TzhaarFightPitsPlugin extends ActivityPlugin {
}
if (e instanceof Player && (player = (Player) e).getAppearance().getSkullIcon() == SKULL_ID) {
player.getAppearance().setSkullIcon(-1);
player.getUpdateMasks().register(new AppearanceFlag(player));
player.updateAppearance();
}
return super.leave(e, logout);
}

View file

@ -41,8 +41,6 @@ import core.game.world.map.zone.MapZone
import core.game.world.map.zone.ZoneBorders
import core.game.world.map.zone.ZoneBuilder
import core.game.world.update.flag.chunk.AnimateObjectUpdateFlag
import core.game.world.update.flag.context.Animation
import core.game.world.update.flag.context.Graphics
import org.rs09.consts.Items
import org.rs09.consts.NPCs
import core.game.dialogue.DialogueFile
@ -55,9 +53,7 @@ import content.global.handlers.iface.ge.StockMarket
import content.global.skill.slayer.SlayerManager
import content.data.consumables.*
import core.game.activity.Cutscene
import core.game.interaction.Clocks
import core.game.interaction.QueueStrength
import core.game.interaction.QueuedScript
import core.game.interaction.*
import core.game.node.entity.player.info.LogType
import core.game.node.entity.player.info.PlayerMonitor
import core.tools.SystemLogger
@ -68,11 +64,13 @@ import core.game.world.GameWorld.Pulser
import core.game.world.map.path.ProjectilePathfinder
import core.game.world.repository.Repository
import core.game.consumable.*
import core.tools.Log
import core.tools.tick
import core.ServerConstants
import core.api.utils.Vector
import core.tools.*
import core.game.world.update.flag.*
import core.game.world.update.flag.context.*
import java.util.regex.*
import java.io.*
import kotlin.math.*
/**
@ -1082,6 +1080,68 @@ fun forceWalk(entity: Entity, dest: Location, type: String) {
path.walk(entity)
}
/**
* Force a player to move from the start location to the dest location
* @param player the player we are moving
* @param start the start location
* @param dest the end location
* @param startArrive the number of client cycles to take moving the player to the start location
* @param destArrive the number of client cycles to take moving the player to the end location from the start location
* @param direction (optional) the direction to face the player during the movement
* @param anim (optional) the animation to use throughout the movement
* @param callback (optional) a callback called when the forced movement completes
* @see NOTE: There are 30 client cycles per second.
*/
fun forceMove (player: Player, start: Location, dest: Location, startArrive: Int, destArrive: Int, dir: Direction? = null, anim: Int = -1, callback: (()->Unit)? = null) {
var direction: Direction
if (dir == null) {
var delta = Location.getDelta(start, dest)
var x = abs(delta.x)
var y = abs(delta.y)
if (x > y)
direction = Direction.getDirection(delta.x, 0)
else
direction = Direction.getDirection(0, delta.y)
} else direction = dir
val startLoc = Location.create(start)
val destLoc = Location.create(dest)
var startArriveTick = getWorldTicks() + cyclesToTicks (startArrive) + 1
var destArriveTick = startArriveTick + cyclesToTicks (destArrive)
var maskSet = false
delayEntity(player, (destArriveTick - getWorldTicks()) + 1)
queueScript (player, 0, QueueStrength.SOFT) { stage: Int ->
if (!finishedMoving(player))
return@queueScript keepRunning(player)
if (!maskSet) {
var ctx = ForceMoveCtx (startLoc, destLoc, startArrive, destArrive, direction)
player.updateMasks.register(EntityFlag.ForceMove, ctx)
maskSet = true
}
var tick = getWorldTicks()
if (tick < startArriveTick) {
return@queueScript keepRunning(player)
} else if (tick < destArriveTick) {
if (animationFinished(player))
animate (player, anim)
return@queueScript keepRunning(player)
} else if (tick >= destArriveTick) {
try {
callback?.invoke()
} catch (e: Exception) {
e.printStackTrace()
}
player.properties.teleportLocation = dest
return@queueScript stopExecuting(player)
}
return@queueScript stopExecuting(player)
}
}
/**
* Interrupts a given entity's walking queue
* @param entity the entity to interrupt
@ -2382,6 +2442,24 @@ fun log(origin: Class<*>, type: Log, message: String) {
SystemLogger.processLogEntry(origin, type, message)
}
/**
* Logs a message to the server console along with a stack trace leading up to it.
* @param origin simply put (Kotlin) this::class.java or (Java) this.getClass()
* @param type the type of log: Log.FINE (default, visible on VERBOSE), Log.INFO (visible on DETAILED), Log.WARN (visible on CAUTIOUS), Log.ERR (always visible)
* @param message the actual message to log.
*/
fun logWithStack(origin: Class<*>, type: Log, message: String) {
try {
throw Exception(message)
} catch (e: Exception) {
val sw = StringWriter()
val pw = PrintWriter(sw)
e.printStackTrace(pw)
log(origin, type, "$sw")
}
}
/**
* Used by content handlers to check if the entity is done moving yet
*/
@ -2403,7 +2481,7 @@ fun delayScript(entity: Entity, ticks: Int): Boolean {
*/
fun delayEntity(entity: Entity, ticks: Int) {
entity.scripts.delay = GameWorld.ticks + ticks
lock(entity, 5) //TODO: REMOVE WHEN EVERYTHING IMPORTANT USES PROPER QUEUES - THIS IS INCORRECT BEHAVIOR
lock(entity, ticks) //TODO: REMOVE WHEN EVERYTHING IMPORTANT USES PROPER QUEUES - THIS IS INCORRECT BEHAVIOR
}
fun apRange(entity: Entity, apRange: Int) {

View file

@ -26,7 +26,7 @@ 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.game.world.update.flag.*
import core.tools.RandomFunction
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -90,7 +90,7 @@ class ScriptAPI(private val bot: Player) {
fun sendChat(message: String) {
bot.sendChat(message)
bot.updateMasks.register(ChatFlag(ChatMessage(bot, message, 0, 0)))
bot.updateMasks.register(EntityFlag.Chat, ChatMessage(bot, message, 0, 0))
}
/**

View file

@ -8,7 +8,6 @@ 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;
@ -306,7 +305,7 @@ public final class EquipmentContainer extends Container {
}
}
player.getAppearance().setAnimations();
player.getUpdateMasks().register(new AppearanceFlag(player));
player.updateAppearance();
player.getSettings().updateWeight();
updateBonuses(player);
}

View file

@ -9,7 +9,6 @@ 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;
@ -145,7 +144,7 @@ public class InteractPlugin {
@Override
public boolean pulse() {
try {
player.faceLocation(FaceLocationFlag.getFaceLocation(player, node));
player.faceLocation(node.getFaceLocation(player.getLocation()));
if (player.getLocks().isInteractionLocked() || player.getZoneMonitor().interact(node, option)) {
return true;
}
@ -181,7 +180,7 @@ public class InteractPlugin {
@Override
public boolean pulse() {
try {
player.faceLocation(FaceLocationFlag.getFaceLocation(player, node));
player.faceLocation(node.getFaceLocation(player.getLocation()));
if (player.getLocks().isInteractionLocked() || player.getZoneMonitor().interact(node, option)) {
return true;
}
@ -344,4 +343,4 @@ public class InteractPlugin {
this.initialized = initialized;
}
}
}

View file

@ -12,7 +12,6 @@ import core.tools.Log;
import core.tools.SystemLogger;
import core.game.system.task.Pulse;
import core.game.world.map.Location;
import core.game.world.update.flag.player.FaceLocationFlag;
import core.plugin.Plugin;
import java.util.ArrayList;
@ -175,7 +174,7 @@ public abstract class UseWithHandler implements Plugin<Object> {
@Override
public boolean pulse() {
event.getPlayer().dispatch(new UseWithEvent(event.getUsed().getId(), event.getUsedWith().getId()));
event.getPlayer().faceLocation(FaceLocationFlag.getFaceLocation(event.getPlayer(), event.getUsedWith()));
event.getPlayer().faceLocation(event.getUsedWith().getFaceLocation(event.getPlayer().getLocation()));
boolean handled = false;
Item used = (Item) event.getUsed();
for (UseWithHandler h : handlers) {
@ -273,4 +272,4 @@ public abstract class UseWithHandler implements Plugin<Object> {
public boolean isDynamic() {
return false;
}
}
}

View file

@ -27,6 +27,7 @@ import core.game.world.map.path.Pathfinder;
import core.game.world.map.zone.ZoneMonitor;
import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.context.Graphics;
import core.game.world.update.flag.*;
import core.game.node.entity.combat.CombatSwingHandler;
import core.game.world.update.UpdateMasks;
@ -48,7 +49,7 @@ public abstract class Entity extends Node {
/**
* The entity's update masks.
*/
private final UpdateMasks updateMasks = new UpdateMasks();
private final UpdateMasks updateMasks = new UpdateMasks(this);
/**
* The walking queue.
@ -631,21 +632,39 @@ public abstract class Entity extends Node {
* @param entity The entity to face.
* @return {@code True} if succesful.
*/
public abstract boolean face(Entity entity);
public boolean face(Entity entity) {
if (entity == null) {
int ordinal = EntityFlags.getOrdinal(EFlagType.of(this), EntityFlag.FaceEntity);
if (getUpdateMasks().unregisterSynced(ordinal)) {
return getUpdateMasks().register(EntityFlag.FaceEntity, null);
}
return true;
}
return getUpdateMasks().register(EntityFlag.FaceEntity, entity, true);
}
/**
* Registers a new face location update flag to the update masks.
* @param location The location to face.
* @return {@code True} if succesful.
*/
public abstract boolean faceLocation(Location location);
public boolean faceLocation(Location location) {
if (location == null) {
int ordinal = EntityFlags.getOrdinal(EFlagType.of(this), EntityFlag.FaceLocation);
getUpdateMasks().unregisterSynced(ordinal);
return true;
}
return getUpdateMasks().register(EntityFlag.FaceLocation, location, true);
}
/**
* Registers a new force chat update flag to the update masks.
* @param string The string.
* @return {@code True} if succesful.
*/
public abstract boolean sendChat(String string);
public boolean sendChat(String string) {
return getUpdateMasks().register(EntityFlag.ForceChat, string);
}
/**
* Gets the current combat swing handler.

View file

@ -6,10 +6,7 @@ import core.game.node.entity.npc.NPC;
import core.game.world.GameWorld;
import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.context.Graphics;
import core.game.world.update.flag.npc.NPCAnimation;
import core.game.world.update.flag.npc.NPCGraphic;
import core.game.world.update.flag.player.AnimationFlag;
import core.game.world.update.flag.player.GraphicFlag;
import core.game.world.update.flag.EntityFlag;
/**
* Handles the animating of an Entity.
@ -128,12 +125,12 @@ public final class Animator {
ticks = 0;
}
entity.clocks[Clocks.getANIMATION_END()] = ticks;
entity.getUpdateMasks().register(entity instanceof NPC ? new NPCAnimation(animation) : new AnimationFlag(animation));
entity.getUpdateMasks().register(EntityFlag.Animate, animation);
priority = animation.getPriority();
}
if (graphic != null) {
this.graphics = graphic;
entity.getUpdateMasks().register(entity instanceof NPC ? new NPCGraphic(graphic) : new GraphicFlag(graphic));
entity.getUpdateMasks().register(EntityFlag.SpotAnim, graphic);
}
return true;
}
@ -212,4 +209,4 @@ public final class Animator {
public void setPriority(Priority priority) {
this.priority = priority;
}
}
}

View file

@ -6,8 +6,8 @@ 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.update.flag.context.Animation;
import core.game.world.update.flag.player.ForceMovementFlag;
import core.game.world.update.flag.context.*;
import core.game.world.update.flag.*;
/**
* The force movement handler.
@ -91,8 +91,9 @@ public class ForceMovement extends Pulse {
* @param commenceSpeed The commencing speed.
* @param pathSpeed The path speed.
* @param unlockAfter Whether to unlock the entity after the ForceMovement completes
*
* @deprecated this is no longer the preferred way to use force movement. Use the ContentAPI forceMove method instead, please.
*/
@Deprecated
public ForceMovement(Entity e, Location start, Location destination, Animation startAnim, Animation animation, Direction direction, int commenceSpeed, int pathSpeed, boolean unlockAfter) {
super(1, e);
this.entity = e;
@ -113,11 +114,17 @@ public class ForceMovement extends Pulse {
* @param end the destination.
* @param animation the animation.
* @param speed The path speed.
* @deprecated this is no longer the preferred way to use force movement. Use the ContentAPI forceMove method instead, please.
*/
@Deprecated
public ForceMovement(Entity e, Location start, Location end, Animation animation, int speed) {
this(e, start, end, WALK_ANIMATION, animation, direction(start, end), WALKING_SPEED, speed, true);
}
/**
* @deprecated this is no longer the preferred way to use force movement. Use the ContentAPI forceMove method instead, please.
*/
@Deprecated
public ForceMovement(Entity e, Location destination, int startSpeed, int animSpeed){
this(e,e.getLocation(),destination,WALK_ANIMATION,WALK_ANIMATION,direction(e.getLocation(),destination),startSpeed,animSpeed, true);
}
@ -128,7 +135,9 @@ public class ForceMovement extends Pulse {
* @param start the start location.
* @param destination the destination.
* @param animation the animation.
* @deprecated this is no longer the preferred way to use force movement. Use the ContentAPI forceMove method instead, please.
*/
@Deprecated
public ForceMovement(Entity e, Location start, Location destination, Animation animation) {
this(e, start, destination, WALK_ANIMATION, animation, direction(start, destination), WALKING_SPEED, WALKING_SPEED, true);
}
@ -138,7 +147,9 @@ public class ForceMovement extends Pulse {
* @param start the start loc.
* @param destination the destination.
* @param animation the animation.
* @deprecated this is no longer the preferred way to use force movement. Use the ContentAPI forceMove method instead, please.
*/
@Deprecated
public ForceMovement(Location start, Location destination, Animation animation) {
this(null, start, destination, WALK_ANIMATION, animation, direction(start, destination), WALKING_SPEED, WALKING_SPEED, true);
}
@ -148,7 +159,9 @@ public class ForceMovement extends Pulse {
* @param e The entity.
* @param destination The destination location.
* @return The created ForceMovement object.
* @deprecated this is no longer the preferred way to use force movement. Use the ContentAPI forceMove method instead, please.
*/
@Deprecated
public static ForceMovement run(Entity e, Location destination) {
return run(e, e.getLocation(), destination, WALK_ANIMATION, WALK_ANIMATION, direction(e.getLocation(), destination), WALKING_SPEED, WALKING_SPEED, true);
}
@ -159,7 +172,9 @@ public class ForceMovement extends Pulse {
* @param start The start location.
* @param destination The destination location.
* @return The created ForceMovement object.
* @deprecated this is no longer the preferred way to use force movement. Use the ContentAPI forceMove method instead, please.
*/
@Deprecated
public static ForceMovement run(Entity e, Location start, Location destination) {
return run(e, start, destination, WALK_ANIMATION, WALK_ANIMATION, direction(e.getLocation(), destination), WALKING_SPEED, WALKING_SPEED, true);
}
@ -171,7 +186,9 @@ public class ForceMovement extends Pulse {
* @param destination The destination location.
* @param animation The animation.
* @return The created ForceMovement object.
* @deprecated this is no longer the preferred way to use force movement. Use the ContentAPI forceMove method instead, please.
*/
@Deprecated
public static ForceMovement run(Entity e, Location start, Location destination, Animation animation) {
return run(e, start, destination, WALK_ANIMATION, animation, direction(start, destination), WALKING_SPEED, WALKING_SPEED, true);
}
@ -184,7 +201,9 @@ public class ForceMovement extends Pulse {
* @param animation The animation.
* @param speed The path speed.
* @return The created ForceMovement object.
* @deprecated this is no longer the preferred way to use force movement. Use the ContentAPI forceMove method instead, please.
*/
@Deprecated
public static ForceMovement run(Entity e, Location start, Location destination, Animation animation, int speed) {
return run(e, start, destination, WALK_ANIMATION, animation, direction(start, destination), WALKING_SPEED, speed, true);
}
@ -196,7 +215,9 @@ public class ForceMovement extends Pulse {
* @param destination The destination location.
* @param animation The animation.
* @return The created ForceMovement object.
* @deprecated this is no longer the preferred way to use force movement. Use the ContentAPI forceMove method instead, please.
*/
@Deprecated
public static ForceMovement run(Entity e, Location start, Location destination, Animation startAnim, Animation animation) {
return run(e, start, destination, startAnim, animation, direction(start, destination), WALKING_SPEED, WALKING_SPEED, true);
}
@ -209,7 +230,9 @@ public class ForceMovement extends Pulse {
* @param animation The animation.
* @param direction The direction.
* @return The created ForceMovement object.
* @deprecated this is no longer the preferred way to use force movement. Use the ContentAPI forceMove method instead, please.
*/
@Deprecated
public static ForceMovement run(Entity e, Location start, Location destination, Animation startAnim, Animation animation, Direction direction) {
return run(e, start, destination, startAnim, animation, direction, WALKING_SPEED, WALKING_SPEED, true);
}
@ -223,11 +246,17 @@ public class ForceMovement extends Pulse {
* @param direction The direction.
* @param pathSpeed The speed (in ticks).
* @return The created ForceMovement object.
* @deprecated this is no longer the preferred way to use force movement. Use the ContentAPI forceMove method instead, please.
*/
@Deprecated
public static ForceMovement run(Entity e, Location start, Location destination, Animation startAnim, Animation animation, Direction direction, int pathSpeed) {
return run(e, start, destination, startAnim, animation, direction, WALKING_SPEED, pathSpeed, true);
}
/**
* @deprecated this is no longer the preferred way to use force movement. Use the ContentAPI forceMove method instead, please.
*/
@Deprecated
public static ForceMovement run(Entity e, Location start, Location destination, Animation startAnim, Animation animation, Direction direction, int commenceSpeed, int pathSpeed) {
return run(e, start, destination, startAnim, animation, direction, commenceSpeed, pathSpeed, true);
}
@ -240,7 +269,9 @@ public class ForceMovement extends Pulse {
* @param animation The animation.
* @param direction The direction.
* @return The created ForceMovement object.
* @deprecated this is no longer the preferred way to use force movement. Use the ContentAPI forceMove method instead, please.
*/
@Deprecated
public static ForceMovement run(Entity e, Location start, Location destination, Animation startAnim, Animation animation, Direction direction, int commenceSpeed, int pathSpeed, boolean unlockAfter) {
if (startAnim != null) {
startAnim.setPriority(Animator.Priority.VERY_HIGH);
@ -254,7 +285,10 @@ public class ForceMovement extends Pulse {
GameWorld.getPulser().submit(fm);
return fm;
}
/*
* @deprecated this is no longer the preferred way to use force movement. Use the ContentAPI forceMove method instead, please.
*/
@Deprecated
public static ForceMovement run(Entity e, Location destination, int commenceSpeed, int pathSpeed){
return run(e,e.getLocation(),destination,WALK_ANIMATION,WALK_ANIMATION,direction(e.getLocation(),destination),commenceSpeed,pathSpeed, true);
}
@ -262,7 +296,9 @@ public class ForceMovement extends Pulse {
/**
* Method used to run the force movement.
* @param e the entity.
* @deprecated this is no longer the preferred way to use force movement. Use the ContentAPI forceMove method instead, please.
*/
@Deprecated
public void run(final Entity e, final int speed) {
this.entity = e;
int commence = (int) start.getDistance(e.getLocation());
@ -321,7 +357,7 @@ public class ForceMovement extends Pulse {
}
int ticks = 1 + commenceSpeed + pathSpeed;
entity.getImpactHandler().setDisabledTicks(ticks);
entity.getUpdateMasks().register(new ForceMovementFlag(this));
entity.getUpdateMasks().register(EntityFlag.ForceMove, new ForceMoveCtx(start, destination, commenceSpeed * 30, pathSpeed * 30, direction));
if(entity instanceof Player) {
entity.getWalkingQueue().updateRegion(destination, false);
}

View file

@ -26,10 +26,7 @@ import core.game.world.map.build.DynamicRegion;
import core.game.world.map.path.Pathfinder;
import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.context.Graphics;
import core.game.world.update.flag.npc.NPCFaceEntity;
import core.game.world.update.flag.npc.NPCFaceLocation;
import core.game.world.update.flag.npc.NPCForceChat;
import core.game.world.update.flag.npc.NPCSwitchId;
import core.game.world.update.flag.*;
import core.tools.RandomFunction;
import core.api.utils.GlobalKillCounter;
import core.api.utils.Vector;
@ -579,31 +576,6 @@ public class NPC extends Entity {
super.reset();
}
@Override
public boolean face(Entity entity) {
if (entity == null) {
if (getUpdateMasks().unregisterSynced(NPCFaceEntity.getOrdinal())) {
return getUpdateMasks().register(new NPCFaceEntity(null));
}
return true;
}
return getUpdateMasks().register(new NPCFaceEntity(entity), true);
}
@Override
public boolean faceLocation(Location location) {
if (location == null) {
getUpdateMasks().unregisterSynced(NPCFaceLocation.getOrdinal());
return true;
}
return getUpdateMasks().register(new NPCFaceLocation(location), true);
}
@Override
public boolean sendChat(String string) {
return getUpdateMasks().register(new NPCForceChat(string));
}
@Override
public CombatSwingHandler getSwingHandler(boolean swing) {
CombatSwingHandler original = getProperties().getCombatPulse().getStyle().getSwingHandler();
@ -703,9 +675,10 @@ public class NPC extends Entity {
configure();
interactPlugin.setDefault();
if (id == originalId) {
getUpdateMasks().unregisterSynced(NPCSwitchId.getOrdinal());
int ordinal = EntityFlags.getOrdinal (EFlagType.NPC, EntityFlag.TypeSwap);
getUpdateMasks().unregisterSynced(ordinal);
}
getUpdateMasks().register(new NPCSwitchId(id), id != originalId);
getUpdateMasks().register(EntityFlag.TypeSwap, id, id != originalId);
return this;
}

View file

@ -43,11 +43,7 @@ import core.game.world.map.build.DynamicRegion;
import core.game.world.map.path.Pathfinder;
import core.game.world.map.zone.ZoneType;
import core.game.world.update.flag.PlayerFlags;
import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.context.Graphics;
import core.game.world.update.flag.player.FaceEntityFlag;
import core.game.world.update.flag.player.FaceLocationFlag;
import core.game.world.update.flag.player.ForceChatFlag;
import core.game.world.update.flag.*;
import core.net.IoSession;
import core.net.packet.PacketRepository;
import core.net.packet.context.DynamicSceneContext;
@ -81,6 +77,7 @@ import core.game.ge.GrandExchangeRecords;
import core.game.ge.GrandExchangeOffer;
import core.cache.def.impl.ItemDefinition;
import core.worker.ManagementEvents;
import core.game.world.update.flag.context.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
@ -549,31 +546,6 @@ public class Player extends Entity {
Arrays.fill(opCounts, (byte) 0);
}
@Override
public boolean face(Entity entity) {
if (entity == null) {
if (getUpdateMasks().unregisterSynced(FaceEntityFlag.getOrdinal())) {
return getUpdateMasks().register(new FaceEntityFlag(entity));
}
return true;
}
return getUpdateMasks().register(new FaceEntityFlag(entity), true);
}
@Override
public boolean faceLocation(Location location) {
if (location == null) {
getUpdateMasks().unregisterSynced(FaceLocationFlag.getOrdinal());
return true;
}
return getUpdateMasks().register(new FaceLocationFlag(location), true);
}
@Override
public boolean sendChat(String string) {
return getUpdateMasks().register(new ForceChatFlag(string));
}
@Override
public int getClientIndex() {
return this.getIndex() | 0x8000;
@ -1393,4 +1365,8 @@ public class Player extends Entity {
}
states.remove(key);
}
public void updateAppearance() {
getUpdateMasks().register(EntityFlag.Appearance, this);
}
}

View file

@ -6,7 +6,6 @@ import core.game.node.entity.player.Player;
import core.game.node.entity.player.link.emote.Emotes;
import core.game.node.item.Item;
import core.game.world.map.RegionManager;
import core.game.world.update.flag.player.AppearanceFlag;
import core.net.packet.PacketRepository;
import core.net.packet.context.InterfaceContext;
import core.net.packet.out.Interface;
@ -136,7 +135,7 @@ public final class LoginConfiguration {
UpdateSequence.getRenderablePlayers().add(player);
RegionManager.move(player);
player.getMusicPlayer().init();
player.getUpdateMasks().register(new AppearanceFlag(player));
player.updateAppearance();
player.getPlayerFlags().setUpdateSceneGraph(true);
player.getStateManager().init();
player.getPacketDispatch().sendInterfaceConfig(226, 1, true);
@ -303,4 +302,4 @@ public final class LoginConfiguration {
return LOGIN_PLUGINS;
}
}
}

View file

@ -12,8 +12,7 @@ import core.game.world.map.RegionManager;
import core.game.world.update.flag.chunk.AnimateObjectUpdateFlag;
import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.context.Graphics;
import core.game.world.update.flag.player.AnimationFlag;
import core.game.world.update.flag.player.GraphicFlag;
import core.game.world.update.flag.EntityFlag;
import core.net.packet.PacketRepository;
import core.net.packet.context.*;
import core.net.packet.context.DisplayModelContext.ModelType;
@ -300,7 +299,7 @@ public final class PacketDispatch {
* @param id The animation id.
*/
public void sendAnimation(int id) {
player.getUpdateMasks().register(new AnimationFlag(new Animation(id)));
player.getUpdateMasks().register(EntityFlag.Animate, new Animation(id));
}
/**
@ -309,7 +308,7 @@ public final class PacketDispatch {
* @param delay The animation delay.
*/
public void sendAnimation(int id, int delay) {
player.getUpdateMasks().register(new AnimationFlag(new Animation(id, delay)));
player.getUpdateMasks().register(EntityFlag.Animate, new Animation(id, delay));
}
/**
@ -317,7 +316,7 @@ public final class PacketDispatch {
* @param id The graphic id.
*/
public void sendGraphic(int id) {
player.getUpdateMasks().register(new GraphicFlag(new Graphics(id)));
player.getUpdateMasks().register(EntityFlag.SpotAnim, new Graphics(id));
}
/**
@ -383,7 +382,7 @@ public final class PacketDispatch {
* @param height The graphic height.
*/
public void sendGraphic(int id, int height) {
player.getUpdateMasks().register(new GraphicFlag(new Graphics(id, height)));
player.getUpdateMasks().register(EntityFlag.SpotAnim, new Graphics(id, height));
}
public void sendVarClient(int id, int value, boolean cs2) {
PacketRepository.send(Config.class, new ConfigContext(player, id, value, cs2));

View file

@ -3,7 +3,6 @@ package core.game.node.entity.player.link;
import core.game.node.entity.Entity;
import core.game.node.entity.player.Player;
import core.game.node.entity.state.EntityState;
import core.game.world.update.flag.player.AppearanceFlag;
import java.util.ArrayList;
import java.util.List;
@ -86,7 +85,7 @@ public final class SkullManager {
*/
public void setSkullIcon(int skullIcon) {
player.getAppearance().setSkullIcon(skullIcon);
player.getUpdateMasks().register(new AppearanceFlag(player));
player.updateAppearance();
}
/**

View file

@ -8,7 +8,6 @@ import core.game.node.entity.impl.Animator.Priority;
import core.game.node.entity.player.Player;
import core.game.node.item.Item;
import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.player.AppearanceFlag;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import core.game.system.config.ItemConfigParser;
@ -190,7 +189,7 @@ public final class Appearance {
* Method used to sync this appearance with the client.
*/
public void sync() {
player.getUpdateMasks().register(new AppearanceFlag(player));
player.updateAppearance();
}
/**

View file

@ -11,7 +11,6 @@ import core.game.node.entity.npc.NPC;
import core.game.node.entity.player.Player;
import core.game.node.entity.player.link.request.assist.AssistSession;
import core.game.node.item.Item;
import core.game.world.update.flag.player.AppearanceFlag;
import core.net.packet.PacketRepository;
import core.net.packet.context.SkillContext;
import core.net.packet.out.SkillLevel;
@ -279,10 +278,8 @@ public final class Skills {
staticLevels[slot] = newLevel;
if (entity instanceof Player) {
if (updateCombatLevel()) {
player.getUpdateMasks().register(new AppearanceFlag(player));
}
LevelUp.levelup(player, slot, amount);
player.updateAppearance();
LevelUp.levelup(player, slot, amount);
}
}
if (entity instanceof Player) {

View file

@ -27,6 +27,10 @@ import java.io.FileWriter
import java.util.Arrays
import core.net.packet.PacketWriteQueue
import core.tools.Log
import core.game.world.update.flag.*
import core.game.world.update.flag.context.*
import core.game.node.entity.impl.*
import core.game.world.map.Location
@Initializable
class DevelopmentCommandSet : CommandSet(Privilege.ADMIN) {
@ -239,5 +243,34 @@ class DevelopmentCommandSet : CommandSet(Privilege.ADMIN) {
define("drawroute", Privilege.ADMIN, "", "Visualizes the path your player is taking") {player, _ ->
setAttribute (player, "routedraw", !getAttribute(player, "routedraw", false))
}
define ("fmstart", Privilege.ADMIN, "", "") {player, _ ->
setAttribute(player, "fmstart", Location.create(player.location))
}
define ("fmend", Privilege.ADMIN, "", "") {player, _ ->
setAttribute(player, "fmend", Location.create(player.location))
}
define ("fmspeed", Privilege.ADMIN, "", "") {player, args ->
setAttribute(player, "fmspeed", args[1].toIntOrNull() ?: 10)
}
define ("fmspeedend", Privilege.ADMIN, "", "") {player, args ->
setAttribute(player, "fmspeedend", args[1].toIntOrNull() ?: 10)
}
define("testfm", Privilege.ADMIN, "", "") { player, _ ->
val start = getAttribute(player, "fmstart", Location.create(player.location))
val end = getAttribute(player, "fmend", Location.create(player.location))
val speed = getAttribute(player, "fmspeed", 10)
val speedEnd = getAttribute(player, "fmspeedend", 10)
val ani = getAttribute(player, "fmanim", -1)
forceMove(player, start, end, speed, speedEnd, anim = ani)
}
define("fmanim", Privilege.ADMIN, "", "") {player, args ->
setAttribute(player, "fmanim", args[1].toIntOrNull() ?: -1)
}
}
}

View file

@ -5,82 +5,42 @@ import core.game.node.entity.combat.ImpactHandler
import core.game.node.entity.player.Player
import core.game.world.update.flag.UpdateFlag
import core.game.world.update.flag.context.HitMark
import core.game.world.update.flag.npc.NPCHitFlag
import core.game.world.update.flag.npc.NPCHitFlag1
import core.game.world.update.flag.player.AppearanceFlag
import core.game.world.update.flag.player.HitUpdateFlag
import core.game.world.update.flag.player.HitUpdateFlag1
import core.game.world.update.flag.*
import core.net.packet.IoBuffer
import core.api.*
import core.tools.*
import java.util.concurrent.atomic.AtomicBoolean
/**
* Holds an entity's update masks.
* @author Emperor
*/
class UpdateMasks {
/**
* The mask data.
*/
private var maskData = 0
/**
* The update masks array.
*/
private val masks = arrayOfNulls<UpdateFlag<*>?>(SIZE)
/**
* Gets the appearanceStamp.
* @return The appearanceStamp.
*/
/**
* Sets the appearanceStamp.
* @param appearanceStamp The appearanceStamp to set.
*/
/**
* The appearance time stamp.
*/
class UpdateMasks (val owner: Entity) {
var appearanceStamp: Long = 0
/**
* The synced mask data.
*/
private var syncedMask = 0
/**
* The update masks array.
*/
private val syncedMasks = arrayOfNulls<UpdateFlag<*>?>(SIZE)
/**
* If the update masks are being updated.
*/
private val type = EFlagType.of (owner)
private val updating = AtomicBoolean()
/**
* Registers an update flag.
* @param flag The update flag.
* @return `True` if successful.
*/
/**
* Registers an update flag.
* @param flag The update flag.
* @return `True` if successful.
*/
private var presenceFlags = 0
private var syncedPresenceFlags = 0
private val elements = arrayOfNulls<MaskElement?>(SIZE)
private val syncedElements = arrayOfNulls<MaskElement?>(SIZE)
private data class MaskElement (val encoder: EFlagProvider, val context: Any?)
@JvmOverloads
fun register(flag: UpdateFlag<*>, synced: Boolean = false): Boolean {
var synced = synced
if (updating.get()) {
fun register(flag: EntityFlag, context: Any?, sync: Boolean = false) : Boolean {
var synced = sync
var provider = EntityFlags.getFlagFor (type, flag)
if (provider == null) {
logWithStack(this::class.java, Log.ERR, "Tried to use flag ${flag.name} which is not available for ${type.name} in this revision.")
return false
}
if (flag is AppearanceFlag) {
if (updating.get())
return false
if (flag == EntityFlag.Appearance) {
appearanceStamp = System.currentTimeMillis()
synced = true
}
if (synced) {
syncedMasks[flag.ordinal()] = flag
syncedMask = syncedMask or flag.data()
syncedElements[provider.ordinal] = MaskElement (provider, context)
syncedPresenceFlags = syncedPresenceFlags or provider.presenceFlag
}
maskData = maskData or flag.data()
masks[flag.ordinal()] = flag
elements[provider.ordinal] = MaskElement (provider, context)
presenceFlags = presenceFlags or provider.presenceFlag
return true
}
@ -90,9 +50,9 @@ class UpdateMasks {
* @return `True` if the mask got removed.
*/
fun unregisterSynced(ordinal: Int): Boolean {
if (syncedMasks[ordinal] != null) {
syncedMask = syncedMask and syncedMasks[ordinal]!!.data().inv()
syncedMasks[ordinal] = null
if (syncedElements[ordinal] != null) {
syncedPresenceFlags = syncedPresenceFlags and syncedElements[ordinal]!!.encoder.presenceFlag.inv()
syncedElements[ordinal] = null
return true
}
return false
@ -105,16 +65,16 @@ class UpdateMasks {
* @param buffer The buffer to write on.
*/
fun write(p: Player?, e: Entity?, buffer: IoBuffer) {
var maskData = maskData
var maskData = presenceFlags
if (maskData >= 0x100) {
maskData = maskData or if (e is Player) 0x10 else 0x8
buffer.put(maskData).put(maskData shr 8)
} else {
buffer.put(maskData)
}
for (i in masks.indices) {
val flag = masks[i]
flag?.writeDynamic(buffer, p)
for (i in elements.indices) {
val element = elements[i]
element?.encoder?.writeToDynamic(buffer, element.context, p!!)
}
}
@ -126,10 +86,11 @@ class UpdateMasks {
* @param appearance If the appearance mask should be written.
*/
fun writeSynced(p: Player?, e: Entity?, buffer: IoBuffer, appearance: Boolean) {
var maskData = maskData
var synced = syncedMask
if (!appearance && synced and AppearanceFlag.getData() != 0) {
synced = synced and AppearanceFlag.getData().inv()
var maskData = presenceFlags
var synced = syncedPresenceFlags
var appearanceFlag = EntityFlags.getPresenceFlag(type, EntityFlag.Appearance)
if (!appearance && synced and appearanceFlag != 0) {
synced = synced and appearanceFlag.inv()
}
maskData = maskData or synced
if (maskData >= 0x100) {
@ -138,15 +99,15 @@ class UpdateMasks {
} else {
buffer.put(maskData)
}
for (i in masks.indices) {
var flag = masks[i]
if (flag == null) {
flag = syncedMasks[i]
if (!appearance && flag is AppearanceFlag) {
for (i in elements.indices) {
var element = elements[i]
if (element == null) {
element = syncedElements[i]
if (!appearance && element != null && element.encoder.flag == EntityFlag.Appearance) {
continue
}
}
flag?.writeDynamic(buffer, p)
element?.encoder?.writeToDynamic(buffer, element.context, p!!)
}
}
@ -173,13 +134,8 @@ class UpdateMasks {
* @param secondary If the hit update is secondary.
*/
private fun registerHitUpdate(e: Entity, impact: ImpactHandler.Impact, secondary: Boolean): HitMark {
val player = e is Player
val mark = HitMark(impact.amount, impact.type.ordinal, e)
if (player) {
register(if (secondary) HitUpdateFlag1(mark) else HitUpdateFlag(mark))
} else {
register(if (secondary) NPCHitFlag1(mark) else NPCHitFlag(mark))
}
register(if (secondary) EntityFlag.SecondaryHit else EntityFlag.PrimaryHit, mark)
return mark
}
@ -187,10 +143,10 @@ class UpdateMasks {
* Resets the update masks.
*/
fun reset() {
for (i in masks.indices) {
masks[i] = null
for (i in elements.indices) {
elements[i] = null
}
maskData = 0
presenceFlags = 0
updating.set(false)
}
@ -203,14 +159,14 @@ class UpdateMasks {
* @return `True` if so.
*/
val isUpdateRequired: Boolean
get() = maskData != 0
get() = presenceFlags != 0
/**
* Checks if synced update masks have been registered.
* @return `True` if so.
*/
fun hasSynced(): Boolean {
return syncedMask != 0
return syncedPresenceFlags != 0
}
companion object {
@ -219,4 +175,4 @@ class UpdateMasks {
*/
const val SIZE = 11
}
}
}

View file

@ -0,0 +1,29 @@
package core.game.world.update.flag
import core.net.packet.IoBuffer
import core.game.node.entity.Entity
import kotlin.reflect.*
import core.api.*
import core.tools.*
enum class EFlagType {
Player,
NPC;
companion object {
@JvmStatic fun of (e: Entity) : EFlagType {
if (e is core.game.node.entity.player.Player) return EFlagType.Player
else return EFlagType.NPC
}
}
}
open class EFlagProvider (val revision: Int, val type: EFlagType, val presenceFlag: Int, val ordinal: Int, val flag: EntityFlag) {
open fun writeTo (buffer: IoBuffer, context: Any?) {}
open fun writeToDynamic (buffer: IoBuffer, context: Any?, e: Entity) {
writeTo (buffer, context)
}
fun logInvalidType (context: Any?, expected: KType) {
logWithStack(this::class.java, Log.ERR, "Invalid context of type ${context?.let { it::class.java.simpleName } ?: "null"} passed to ${this::class.simpleName} flag which expects $expected.")
}
}

View file

@ -0,0 +1,16 @@
package core.game.world.update.flag
enum class EntityFlag {
Chat,
ForceChat,
PrimaryHit,
SecondaryHit,
Animate,
SpotAnim,
Appearance,
FaceEntity,
FaceLocation,
ForceMove,
AnimSeq,
TypeSwap
}

View file

@ -0,0 +1,42 @@
package core.game.world.update.flag
import core.api.*
import core.ServerConstants
import kotlin.reflect.*
import kotlin.reflect.full.*
object EntityFlags {
lateinit var flagProviders : HashMap<Int, EFlagProvider>
init {
flagProviders = HashMap()
registerFlagProviders (PlayerFlags530::class)
registerFlagProviders (NPCFlags530::class)
}
fun registerFlagProviders (parent: KClass<*>) {
val clazzes = parent.sealedSubclasses
for (clazz in clazzes) {
val p = clazz.primaryConstructor?.call() as? EFlagProvider ?: continue
flagProviders[getMapToken(p.revision, p.type, p.flag)] = p
}
}
fun getFlagFor (type: EFlagType, flag: EntityFlag) : EFlagProvider? {
val revision = 530
return flagProviders[getMapToken(revision, type, flag)]
}
@JvmStatic fun getOrdinal (type: EFlagType, flag: EntityFlag) : Int {
return getFlagFor(type, flag)?.ordinal ?: -1
}
@JvmStatic fun getPresenceFlag (type: EFlagType, flag: EntityFlag) : Int {
return getFlagFor(type, flag)?.presenceFlag ?: 0
}
private fun getMapToken (revision: Int, type: EFlagType, flag: EntityFlag) : Int {
var token = ((revision shl 8) + (type.ordinal shl 4) + flag.ordinal)
return token
}
}

View file

@ -0,0 +1,108 @@
package core.game.world.update.flag
import core.net.packet.IoBuffer
import core.game.world.update.flag.context.*
import core.game.world.update.flag.*
import core.game.world.map.Location
import core.game.node.entity.Entity
import core.game.node.entity.impl.ForceMovement
import core.game.node.entity.player.Player
import core.tools.*
import core.api.*
import java.nio.charset.StandardCharsets
import kotlin.reflect.*
sealed class NPCFlags530 (p: Int, o: Int, f: EntityFlag) : EFlagProvider (530, EFlagType.NPC, p, o, f) {
class PrimaryHit : NPCFlags530 (0x40, 0, EntityFlag.PrimaryHit) {
override fun writeTo (buffer: IoBuffer, context: Any?) {
if (context !is HitMark) {
logInvalidType (context, typeOf<HitMark>())
return
}
buffer.p1 (context.damage)
buffer.p1neg (context.type)
val e = context.entity
var ratio = 255
val max = e.skills.maximumLifepoints
if (e.skills.lifepoints < max)
ratio = e.skills.lifepoints * 255 / max
buffer.p1sub (ratio)
}
}
class SecondaryHit : NPCFlags530 (0x2, 1, EntityFlag.SecondaryHit) {
override fun writeTo (buffer: IoBuffer, context: Any?) {
if (context !is HitMark) {
logInvalidType (context, typeOf<HitMark>())
return
}
buffer.p1neg (context.damage)
buffer.p1sub (context.type)
}
}
class Animate : NPCFlags530 (0x10, 2, EntityFlag.Animate) {
override fun writeTo (buffer: IoBuffer, context: Any?) {
if (context !is Animation) {
logInvalidType (context, typeOf<Animation>())
return
}
buffer.p2 (context.id)
buffer.p1 (context.delay)
}
}
class FaceEntity : NPCFlags530 (0x4, 3, EntityFlag.FaceEntity) {
override fun writeTo (buffer: IoBuffer, context: Any?) {
if (context !is Entity?) {
logInvalidType (context, typeOf<Entity>())
return
}
if (context == null) {
buffer.p2add (-1)
} else {
buffer.p2add (context.getClientIndex())
}
}
}
class SpotAnim : NPCFlags530 (0x80, 4, EntityFlag.SpotAnim) {
override fun writeTo (buffer: IoBuffer, context: Any?) {
if (context !is Graphics) {
logInvalidType (context, typeOf<Graphics>())
return
}
buffer.p2add (context.id)
buffer.ip4 ((context.height shl 16) + context.delay)
}
}
class TypeSwap : NPCFlags530 (0x1, 5, EntityFlag.TypeSwap) {
override fun writeTo (buffer: IoBuffer, context: Any?) {
if (context !is Int) {
logInvalidType (context, typeOf<Graphics>())
return
}
buffer.ip2 (context)
}
}
class ForceChat : NPCFlags530 (0x20, 6, EntityFlag.ForceChat) {
override fun writeTo (buffer: IoBuffer, context: Any?) {
if (context !is String) {
logInvalidType (context, typeOf<String>())
return
}
buffer.putString (context)
}
}
class AnimationSequence : NPCFlags530 (0x100, 7, EntityFlag.AnimSeq) {
//TODO
}
class FaceLocation : NPCFlags530 (0x200, 8, EntityFlag.FaceLocation) {
override fun writeTo (buffer: IoBuffer, context: Any?) {
if (context !is Location) {
logInvalidType (context, typeOf<Location>())
return
}
buffer.p2add ((context.x shl 1) + 1)
buffer.p2 ((context.y shl 1) + 1)
}
}
}

View file

@ -0,0 +1,195 @@
package core.game.world.update.flag
import core.net.packet.IoBuffer
import core.game.world.update.flag.context.*
import core.game.world.update.flag.*
import core.game.world.map.Location
import core.game.node.entity.Entity
import core.game.node.entity.impl.ForceMovement
import core.game.node.entity.player.Player
import core.tools.*
import core.api.*
import java.nio.charset.StandardCharsets
import kotlin.reflect.*
sealed class PlayerFlags530 (p: Int, o: Int, f: EntityFlag) : EFlagProvider (530, EFlagType.Player, p, o, f) {
class Chat : PlayerFlags530 (0x80, 0, EntityFlag.Chat) {
override fun writeTo (buffer: IoBuffer, context: Any?) {
if (context !is ChatMessage) {
logInvalidType (context, typeOf<ChatMessage>())
return
}
buffer.ip2 (context.effects)
if (context.isQuickChat)
buffer.p1 (4)
else
buffer.p1 (context.chatIcon)
val chatBuf = ByteArray(256)
chatBuf[0] = context.text.length.toByte()
val offset = 1 + StringUtils.encryptPlayerChat (
chatBuf,
0, 1,
context.text.length,
context.text.toByteArray(StandardCharsets.UTF_8)
)
buffer.p1 (offset + 1)
buffer.putReverse (chatBuf, 0, offset)
}
}
class PrimaryHit : PlayerFlags530 (0x1, 1, EntityFlag.PrimaryHit) {
override fun writeTo (buffer: IoBuffer, context: Any?) {
if (context !is HitMark) {
logInvalidType (context, typeOf<HitMark>())
return
}
buffer.psmarts (context.damage)
buffer.p1add (context.type)
var ratio = 255
var e = context.entity
var max = e.skills.maximumLifepoints
if (max > e.skills.lifepoints)
ratio = e.skills.lifepoints * 255 / max
buffer.p1sub (ratio)
}
}
class Animate : PlayerFlags530 (0x8, 2, EntityFlag.Animate) {
override fun writeTo (buffer: IoBuffer, context: Any?) {
if (context !is Animation) {
logInvalidType (context, typeOf<Animation>())
return
}
buffer.p2 (context.id)
buffer.p1 (context.delay)
}
}
class Appearance : PlayerFlags530 (0x4, 3, EntityFlag.Appearance) {
override fun writeTo (buffer: IoBuffer, context: Any?) {
if (context !is Player) {
logInvalidType (context, typeOf<Player>())
return
}
val appearance = context.appearance
appearance.prepareBodyData(context)
var settings = appearance.gender.toByte().toInt()
val nonPvp = context.skullManager.isWilderness && context.skullManager.isWildernessDisabled
if (context.size() > 1)
settings += (context.size() - 1) shl 3
if (nonPvp)
settings += 0x4 //flag the player as being non-pvp, which shows skill level instead of combat level.
buffer.p1 (0) //stand-in for size.
val startPos = buffer.toByteBuffer().position()
buffer.p1 (settings)
buffer.p1 (appearance.skullIcon)
buffer.p1 (appearance.headIcon)
val npcId = appearance.npcId
if (npcId == -1) {
val parts = appearance.bodyParts
for (i in 0 until 12) {
if (parts[i] == 0) buffer.p1 (0)
else buffer.p2 (parts[i])
}
} else {
buffer.p2 (-1)
buffer.p2 (npcId)
buffer.p1 (255)
}
arrayOf ( appearance.hair, appearance.torso, appearance.legs, appearance.feet, appearance.skin ).forEach { part ->
buffer.p1 (part.color)
}
buffer.p2 (appearance.renderAnimation)
buffer.p8 (StringUtils.stringToLong(context.username))
if (nonPvp) {
buffer.p1 (context.properties.currentCombatLevel) //with summoning
buffer.p2 (context.skills.getTotalLevel())
} else {
buffer.p1 (context.properties.currentCombatLevel) //without summoning
buffer.p1 (context.properties.combatLevel) //with summoning
buffer.p1 (context.skullManager.level) //combat range
}
buffer.p1 (0) //this is the sound radius, and if set, 4 shorts need to be set as well which include the sound IDs
//to play to everyone in that radius when the player is rendered onscreen for the first time.
buffer.psizeadd (buffer.toByteBuffer().position() - startPos)
}
}
class FaceEntity : PlayerFlags530 (0x2, 4, EntityFlag.FaceEntity) {
override fun writeTo (buffer: IoBuffer, context: Any?) {
if (context !is Entity?) {
logInvalidType (context, typeOf<Entity>())
return
}
if (context == null)
buffer.p2add (-1)
else
buffer.p2add (context.getClientIndex())
}
}
class ForceMove : PlayerFlags530 (0x400, 5, EntityFlag.ForceMove) {
override fun writeToDynamic (buffer: IoBuffer, context: Any?, e: Entity) {
if (context !is ForceMoveCtx) {
logInvalidType (context, typeOf<ForceMoveCtx>())
return
}
if (e !is Player) {
logInvalidType (context, typeOf<Player>())
return
}
val l = e.playerFlags.getLastSceneGraph()
//start location
buffer.p1neg (context.start.getSceneX(l))
buffer.p1 (context.start.getSceneY(l))
//end location
buffer.p1add (context.dest.getSceneX(l))
buffer.p1 (context.dest.getSceneY(l))
//arrival times (in client cycles)
buffer.ip2 (context.startArrive) //# of client cycles to start location
buffer.ip2 (context.startArrive + context.destArrive) //# of client cycles to end location
buffer.p1neg (context.direction.toInteger()) //direction of movement
}
}
class ForceChat : PlayerFlags530 (0x20, 6, EntityFlag.ForceChat) {
override fun writeTo (buffer: IoBuffer, context: Any?) {
if (context !is String) {
logInvalidType (context, typeOf<String>())
return
}
buffer.putString(context)
}
}
class SecondaryHit : PlayerFlags530 (0x200, 7, EntityFlag.SecondaryHit) {
override fun writeTo (buffer: IoBuffer, context: Any?) {
if (context !is HitMark) {
logInvalidType (context, typeOf<HitMark>())
return
}
buffer.psmarts (context.damage)
buffer.p1sub (context.type)
}
}
class AnimationSequence : PlayerFlags530 (0x800, 8, EntityFlag.AnimSeq) {
//TODO
}
class SpotAnim : PlayerFlags530 (0x100, 9, EntityFlag.SpotAnim) {
override fun writeTo (buffer: IoBuffer, context: Any?) {
if (context !is Graphics) {
logInvalidType (context, typeOf<Graphics>())
return
}
buffer.ip2 (context.id)
buffer.mp4 ((context.height shl 16) + context.delay)
}
}
class FaceLocation : PlayerFlags530 (0x40, 10, EntityFlag.FaceLocation) {
override fun writeTo (buffer: IoBuffer, context: Any?) {
if (context !is Location) {
logInvalidType (context, typeOf<Location>())
return
}
buffer.p2 ((context.x shl 1) + 1)
buffer.ip2add ((context.y shl 1) + 1)
}
}
}

View file

@ -1,6 +1,7 @@
package core.game.world.update.flag.context
import core.game.node.entity.player.Player
import core.game.node.entity.player.info.Rights
/**
* Represents a chat message.
@ -35,6 +36,8 @@ class ChatMessage
var numChars = numChars
private set
var chatIcon = Rights.getChatIcon(player)
@JvmField
var isQuickChat = false
}
}

View file

@ -0,0 +1,5 @@
package core.game.world.update.flag.context
import core.game.world.map.*
data class ForceMoveCtx (val start: Location, val dest: Location, val startArrive: Int, val destArrive: Int, val direction: Direction)

View file

@ -27,6 +27,8 @@ public class HitMark {
*/
private final Entity entity;
public boolean showHealthBar = true;
/**
* Constructs a new {@code HitMark} {@code Object}.
* @param damage The amount of damage.
@ -39,6 +41,13 @@ public class HitMark {
this.entity = entity;
}
public HitMark(int damage, int type, Entity entity, boolean showHealthBar) {
this.damage = damage;
this.type = type;
this.entity = entity;
this.showHealthBar = showHealthBar;
}
/**
* Gets the damage.
* @return The damage.
@ -77,4 +86,4 @@ public class HitMark {
public void setLifepoints(int lifepoints) {
this.lifepoints = lifepoints;
}
}
}

View file

@ -1,45 +0,0 @@
package core.game.world.update.flag.npc;
import core.game.world.update.flag.UpdateFlag;
import core.game.world.update.flag.context.Animation;
import core.net.packet.IoBuffer;
/**
* The NPC animation update flag.
* @author Emperor
*
*/
public final class NPCAnimation extends UpdateFlag<Animation> {
/**
* Constructs a new {@code NPCAnimation} {@code Object}.
* @param context The animation.
*/
public NPCAnimation(Animation context) {
super(context);
}
@Override
public void write(IoBuffer buffer) {
buffer.putShort(context.getId()).put(context.getDelay());
}
@Override
public int data() {
return maskData();
}
@Override
public int ordinal() {
return 2;
}
/**
* Gets the mask data.
* @return The mask data.
*/
public static int maskData() {
return 0x10;
}
}

View file

@ -1,45 +0,0 @@
package core.game.world.update.flag.npc;
import core.game.node.entity.Entity;
import core.game.world.update.flag.UpdateFlag;
import core.net.packet.IoBuffer;
/**
* The face entity update flag for NPCs.
* @author Emperor
*
*/
public final class NPCFaceEntity extends UpdateFlag<Entity> {
/**
* Constructs a new {@code NPCFaceEntity} {@code Object}.
* @param context The context.
*/
public NPCFaceEntity(Entity context) {
super(context);
}
@Override
public void write(IoBuffer buffer) {
buffer.putShortA(context == null ? -1 : context.getClientIndex());
}
@Override
public int data() {
return 0x4;
}
@Override
public int ordinal() {
return getOrdinal();
}
/**
* Gets the mask data.
* @return The mask data.
*/
public static int getOrdinal() {
return 3;
}
}

View file

@ -1,45 +0,0 @@
package core.game.world.update.flag.npc;
import core.game.world.map.Location;
import core.game.world.update.flag.UpdateFlag;
import core.net.packet.IoBuffer;
/**
* The NPC face coordinates update flag.
* @author Emperor
*
*/
public final class NPCFaceLocation extends UpdateFlag<Location> {
/**
* Constructs a new {@code NPCFaceLocation} {@code Object}.
* @param context The location to face.
*/
public NPCFaceLocation(Location context) {
super(context);
}
@Override
public void write(IoBuffer buffer) {
buffer.putShortA((context.getX() << 1) + 1).putShort((context.getY() << 1) + 1);
}
@Override
public int data() {
return 0x200;
}
@Override
public int ordinal() {
return getOrdinal();
}
/**
* Gets the mask ordinal.
* @return The ordinal.
*/
public static int getOrdinal() {
return 8;
}
}

View file

@ -1,43 +0,0 @@
package core.game.world.update.flag.npc;
import core.game.world.update.flag.UpdateFlag;
import core.net.packet.IoBuffer;
/**
* The NPC force chat update flag.
* @author Emperor
*
*/
public final class NPCForceChat extends UpdateFlag<String> {
/**
* Constructs a new {@code NPCForceChat} {@code Object}.
* @param context The chat message.
*/
public NPCForceChat(String context) {
super(context);
}
@Override
public void write(IoBuffer buffer) {
buffer.putString(context);
}
@Override
public int data() {
return maskData();
}
@Override
public int ordinal() {
return 6;
}
/**
* Gets the mask data.
* @return The mask data.
*/
public static int maskData() {
return 0x20;
}
}

View file

@ -1,45 +0,0 @@
package core.game.world.update.flag.npc;
import core.game.world.update.flag.UpdateFlag;
import core.game.world.update.flag.context.Graphics;
import core.net.packet.IoBuffer;
/**
* Handles an NPC's graphic update flag.
* @author Emperor
*
*/
public final class NPCGraphic extends UpdateFlag<Graphics> {
/**
* Constructs a new {@code NPCGraphic} {@code Object}.
* @param context The graphics.
*/
public NPCGraphic(Graphics context) {
super(context);
}
@Override
public void write(IoBuffer buffer) {
buffer.putShortA(context.getId()).putLEInt(context.getHeight() << 16 | context.getDelay());
}
@Override
public int data() {
return maskData();
}
@Override
public int ordinal() {
return 4;
}
/**
* Gets the mask data.
* @return The mask data.
*/
public static int maskData() {
return 0x80;
}
}

View file

@ -1,56 +0,0 @@
package core.game.world.update.flag.npc;
import core.game.node.entity.Entity;
import core.game.world.update.flag.UpdateFlag;
import core.game.world.update.flag.context.HitMark;
import core.net.packet.IoBuffer;
/**
* The NPC's main hit update flag.
* @author Emperor
*
*/
public final class NPCHitFlag extends UpdateFlag<HitMark> {
/**
* Constructs a new {@code NPCHitFlag} {@code Object}.
* @param context The hit mark.
*/
public NPCHitFlag(HitMark context) {
super(context);
}
@Override
public void write(IoBuffer buffer) {
Entity e = context.getEntity();
int max = e.getSkills().getMaximumLifepoints();
int ratio = 0;
if (max > 0) {
if (max < e.getSkills().getLifepoints()) {
ratio = 255;
} else {
ratio = e.getSkills().getLifepoints() * 255 / max;
}
}
buffer.put(context.getDamage()).putC(context.getType()).putS(ratio);
}
@Override
public int data() {
return maskData();
}
@Override
public int ordinal() {
return 0;
}
/**
* Gets the mask data.
* @return The mask data.
*/
public static int maskData() {
return 0x40;
}
}

View file

@ -1,45 +0,0 @@
package core.game.world.update.flag.npc;
import core.game.world.update.flag.UpdateFlag;
import core.game.world.update.flag.context.HitMark;
import core.net.packet.IoBuffer;
/**
* The NPC's supporting hit update flag.
* @author Emperor
*
*/
public class NPCHitFlag1 extends UpdateFlag<HitMark> {
/**
* Constructs a new {@code NPCHitFlag1} {@code Object}.
* @param context The hit mark.
*/
public NPCHitFlag1(HitMark context) {
super(context);
}
@Override
public void write(IoBuffer buffer) {
buffer.putC(context.getDamage()).putS(context.getType());
}
@Override
public int data() {
return maskData();
}
@Override
public int ordinal() {
return 1;
}
/**
* Gets the mask data.
* @return The mask data.
*/
public static int maskData() {
return 0x2;
}
}

View file

@ -1,44 +0,0 @@
package core.game.world.update.flag.npc;
import core.game.world.update.flag.UpdateFlag;
import core.net.packet.IoBuffer;
/**
* The switch NPC id update flag.
* @author Emperor
*
*/
public final class NPCSwitchId extends UpdateFlag<Integer> {
/**
* Constructs a new {@code NPCSwitchId} {@code Object}.
* @param context The new NPC id.
*/
public NPCSwitchId(int context) {
super(context);
}
@Override
public void write(IoBuffer buffer) {
buffer.putLEShort(context);
}
@Override
public int data() {
return 0x1;
}
@Override
public int ordinal() {
return getOrdinal();
}
/**
* Gets the mask ordinal.
* @return The ordinal.
*/
public static int getOrdinal() {
return 5;
}
}

View file

@ -1,43 +0,0 @@
package core.game.world.update.flag.player;
import core.game.world.update.flag.UpdateFlag;
import core.game.world.update.flag.context.Animation;
import core.net.packet.IoBuffer;
/**
* Handles the animation update flag.
* @author Emperor
*/
public final class AnimationFlag extends UpdateFlag<Animation> {
/**
* Constructs a new {@code AnimationFlag} {@code Object}.
*/
public AnimationFlag(Animation context) {
super(context);
}
@Override
public void write(IoBuffer buffer) {
buffer.putShort(context.getId());
buffer.put(context.getDelay());
}
@Override
public int data() {
return maskData();
}
@Override
public int ordinal() {
return 2;
}
/**
* Gets the mask data of the animation update flag.
* @return The mask data.
*/
public static int maskData() {
return 0x8;
}
}

View file

@ -1,91 +0,0 @@
package core.game.world.update.flag.player;
import core.game.node.entity.player.Player;
import core.game.node.entity.player.link.appearance.Appearance;
import core.game.node.entity.player.link.appearance.BodyPart;
import core.game.world.update.flag.UpdateFlag;
import core.net.packet.IoBuffer;
import core.tools.StringUtils;
/**
* Handles the appearance update flag.
* @author Emperor
*
*/
public final class AppearanceFlag extends UpdateFlag<Player> {
/**
* Constructs a new {@code AppearanceFlag} {@code Object}.
* @param player The player.
*/
public AppearanceFlag(Player player) {
super(player);
}
@Override
public void write(IoBuffer buffer) {
Appearance appearance = context.getAppearance();
appearance.prepareBodyData(context);
IoBuffer block = new IoBuffer();
int settings = appearance.getGender().toByte();
if (context.size() > 1) {
settings |= (context.size() - 1) << 3;
}
block.put(settings); // settings hash.
block.put(appearance.getSkullIcon()); // Skull icon
block.put(appearance.getHeadIcon()); // Head icon
int npcId = appearance.getNpcId();
if (npcId == -1) {
int[] parts = appearance.getBodyParts();
for (int i = 0; i < 12; i++) {
int value = parts[i];
if (value == 0) {
block.put(0);
} else {
block.putShort(value);
}
}
} else {
block.putShort(-1);
block.putShort(npcId);
block.put(255);
}
final BodyPart[] colors = new BodyPart[] { appearance.getHair(), appearance.getTorso(), appearance.getLegs(), appearance.getFeet(), appearance.getSkin() };
for (int i = 0; i < colors.length; i++) {// colours
block.put(colors[i].getColor());
}
block.putShort(appearance.getRenderAnimation());
block.putLong(StringUtils.stringToLong(context.getUsername()));
block.put(context.getProperties().getCurrentCombatLevel());
block.putShort(0);
block.put(0);
buffer.putA(block.toByteBuffer().position());
buffer.put(block);
}
@Override
public int data() {
return getData();
}
@Override
public int ordinal() {
return getOrdinal();
}
/**
* Gets the ordinal for this flag.
* @return The flag ordinal.
*/
public static int getOrdinal() {
return 3;
}
/**
* Gets the mask data.
* @return The mask data.
*/
public static int getData() {
return 0x4;
}
}

View file

@ -1,59 +0,0 @@
package core.game.world.update.flag.player;
import core.game.node.entity.player.info.Rights;
import core.game.world.update.flag.UpdateFlag;
import core.game.world.update.flag.context.ChatMessage;
import core.net.packet.IoBuffer;
import core.tools.StringUtils;
import java.nio.charset.StandardCharsets;
/**
* Handles the chat flag.
* @author Emperor
*/
public class ChatFlag extends UpdateFlag<ChatMessage> {
/**
* Constructs a new {@code ChatFlag.java} {@code Object}.
* @param context The context.
*/
public ChatFlag(ChatMessage context) {
super(context);
}
@Override
public void write(IoBuffer buffer) {
byte[] chatStr = new byte[256];
chatStr[0] = (byte) context.getText().length();
int offset = 1 + StringUtils.encryptPlayerChat(chatStr, 0, 1, context.getText().length(), context.getText().getBytes(StandardCharsets.UTF_8));
buffer.putLEShort(context.getEffects()); // 0x8000 does something (you'd
// need to send something
// extra.
if(context.isQuickChat){
buffer.put((byte) 4);
} else {
buffer.put((byte) Rights.getChatIcon(context.getPlayer()));
}
buffer.put(offset + 1);
buffer.putReverse(chatStr, 0, offset);
}
@Override
public int data() {
return maskData();
}
@Override
public int ordinal() {
return 0;
}
/**
* Gets the mask data of the chat update flag.
* @return The mask data.
*/
public static int maskData() {
return 0x80;
}
}

View file

@ -1,45 +0,0 @@
package core.game.world.update.flag.player;
import core.game.node.entity.Entity;
import core.game.world.update.flag.UpdateFlag;
import core.net.packet.IoBuffer;
/**
* The face entity update flag.
* @author Emperor
*
*/
public final class FaceEntityFlag extends UpdateFlag<Entity> {
/**
* Constructs a new {@code FaceEntityFlag} {@code Object}.
* @param context The entity to face.
*/
public FaceEntityFlag(Entity context) {
super(context);
}
@Override
public void write(IoBuffer buffer) {
buffer.putShortA(context == null ? -1 : context.getClientIndex());
}
@Override
public int data() {
return 0x2;
}
@Override
public int ordinal() {
return getOrdinal();
}
/**
* Gets the mask ordinal.
* @return The ordinal.
*/
public static int getOrdinal() {
return 4;
}
}

View file

@ -1,84 +0,0 @@
package core.game.world.update.flag.player;
import core.game.node.Node;
import core.game.node.scenery.Scenery;
import core.game.world.map.Location;
import core.game.world.update.flag.UpdateFlag;
import core.net.packet.IoBuffer;
/**
* Handles the face coordinates update flag.
* @author Emperor
*/
public class FaceLocationFlag extends UpdateFlag<Location> {
/**
* Constructs a new {@code FaceLocationFlag} {@code Object}.
* @param location The location to face.
*/
public FaceLocationFlag(Location location) {
super(location);
}
/**
* Gets the face location of the node.
* @param n The facing node.
* @param node The node to face.
* @return The location.
*/
public static Location getFaceLocation(Node n, Node node) {
int x = node.size() >> 1;
int y = node.size() >> 1;
if (node instanceof Scenery) {
Scenery o = (Scenery) node;
x = o.getDefinition().sizeX >> 1;
y = o.getDefinition().sizeY >> 1;
if (o.getRotation() % 2 != 0) {
x = y;
y = o.getDefinition().sizeX >> 1;
}
if (n.getLocation().equals(o.getLocation()) && node.size() == 1) {
switch (o.getRotation()) {
case 0:
x -= 1;
break;
case 1:
y += 1;
break;
case 2:
x += 1;
break;
case 3:
y -= 1;
break;
}
}
}
return node.getLocation().transform(x, y, 0);
}
@Override
public void write(IoBuffer buffer) {
buffer.putShort((context.getX() << 1) + 1);
buffer.putLEShortA((context.getY() << 1) + 1);
}
@Override
public int data() {
return 0x40;
}
@Override
public int ordinal() {
return getOrdinal();
}
/**
* Gets the mask ordinal.
* @return The ordinal.
*/
public static int getOrdinal() {
return 10;
}
}

View file

@ -1,44 +0,0 @@
package core.game.world.update.flag.player;
import core.game.world.update.flag.UpdateFlag;
import core.net.packet.IoBuffer;
/**
* The force chat update flag.
* @author Emperor
*
*/
public final class ForceChatFlag extends UpdateFlag<String> {
/**
* Constructs a new {@code ForceChatFlag} {@code Object}.
* @param context The chat message.
*/
public ForceChatFlag(String context) {
super(context);
}
@Override
public void write(IoBuffer buffer) {
buffer.putString(context);
}
@Override
public int data() {
return maskData();
}
@Override
public int ordinal() {
return 6;
}
/**
* Gets the mask data.
* @return The mask data.
*/
public static int maskData() {
return 0x20;
}
}

View file

@ -1,60 +0,0 @@
package core.game.world.update.flag.player;
import core.game.node.entity.Entity;
import core.game.node.entity.impl.ForceMovement;
import core.game.node.entity.player.Player;
import core.game.world.map.Location;
import core.game.world.update.flag.UpdateFlag;
import core.net.packet.IoBuffer;
/**
* Handles the force movement player update flag.
* @author Emperor
*
*/
public final class ForceMovementFlag extends UpdateFlag<ForceMovement> {
/**
* Constructs a new {@code ForceMovementFlag} {@code Object}.
* @param forceMovement The force movement data.
*/
public ForceMovementFlag(ForceMovement forceMovement) {
super(forceMovement);
}
@Override
public void write(IoBuffer buffer) {
}
@Override
public void writeDynamic(IoBuffer buffer, Entity e) {
Location l = ((Player) e).getPlayerFlags().getLastSceneGraph();
buffer.putC(context.getStart().getSceneX(l)) // Start location
.put(context.getStart().getSceneY(l)).putA(context.getDestination().getSceneX(l)) // Destination
// location
.put(context.getDestination().getSceneY(l)).putLEShort(context.getCommenceSpeed() * 30)// Commencing
// speed
.putLEShort((context.getCommenceSpeed() * 30) + (context.getPathSpeed() * 30 + 1)) // Path
// speed
.putC(context.getDirection().toInteger());
}
@Override
public int data() {
return maskData();
}
@Override
public int ordinal() {
return 5;
}
/**
* Gets the mask data.
* @return The mask data.
*/
public static int maskData() {
return 0x400;
}
}

View file

@ -1,45 +0,0 @@
package core.game.world.update.flag.player;
import core.game.world.update.flag.UpdateFlag;
import core.game.world.update.flag.context.Graphics;
import core.net.packet.IoBuffer;
/**
* Handles the graphic update flag.
* @author Emperor
*
*/
public final class GraphicFlag extends UpdateFlag<Graphics> {
/**
* Constructs a new {@code GraphicFlag} {@code Object}.
* @param context The context.
*/
public GraphicFlag(Graphics context) {
super(context);
}
@Override
public void write(IoBuffer buffer) {
buffer.putLEShort(context.getId());
buffer.putIntB(context.getHeight() << 16 | context.getDelay());
}
@Override
public int data() {
return maskData();
}
@Override
public int ordinal() {
return 9;
}
/**
* Gets the mask data of the graphic update flag.
* @return The mask data.
*/
public static int maskData() {
return 0x100;
}
}

View file

@ -1,52 +0,0 @@
package core.game.world.update.flag.player;
import core.game.node.entity.Entity;
import core.game.world.update.flag.UpdateFlag;
import core.game.world.update.flag.context.HitMark;
import core.net.packet.IoBuffer;
/**
* The main hit update flag.
* @author Emperor
*
*/
public final class HitUpdateFlag extends UpdateFlag<HitMark> {
/**
* Constructs a new {@code HitUpdateFlag} {@code Object}.
* @param context The hit mark.
*/
public HitUpdateFlag(HitMark context) {
super(context);
}
@Override
public void write(IoBuffer buffer) {
Entity e = context.getEntity();
int max = e.getSkills().getMaximumLifepoints();
int ratio = 255;
if (max > e.getSkills().getLifepoints()) {
ratio = e.getSkills().getLifepoints() * 255 / max;
}
buffer.putSmart(context.getDamage()).putA(context.getType()).putS(ratio);
}
@Override
public int data() {
return maskData();
}
@Override
public int ordinal() {
return 1;
}
/**
* Gets the mask data.
* @return The mask data.
*/
public static int maskData() {
return 0x1;
}
}

View file

@ -1,45 +0,0 @@
package core.game.world.update.flag.player;
import core.game.world.update.flag.UpdateFlag;
import core.game.world.update.flag.context.HitMark;
import core.net.packet.IoBuffer;
/**
* The supportive hit update flag.
* @author Emperor
*
*/
public final class HitUpdateFlag1 extends UpdateFlag<HitMark> {
/**
* Constructs a new {@code HitUpdateFlag1} {@code Object}.
* @param context The hit mark.
*/
public HitUpdateFlag1(HitMark context) {
super(context);
}
@Override
public void write(IoBuffer buffer) {
buffer.putSmart(context.getDamage()).putS(context.getType());
}
@Override
public int data() {
return maskData();
}
@Override
public int ordinal() {
return 7;
}
/**
* Gets the mask data.
* @return The mask data.
*/
public static int maskData() {
return 0x200;
}
}

View file

@ -92,6 +92,219 @@ public class IoBuffer {
return this;
}
/**
* What follows are put/get methods using authentic naming.
* The older methods are kept for the sake of backwards compatibility within the codebase.
*/
public IoBuffer p1 (int value) {
buf.put((byte) value);
return this;
}
public IoBuffer p1add (int value) {
buf.put((byte) (value + 128));
return this;
}
public IoBuffer p1sub (int value) {
buf.put((byte) (128 - value));
return this;
}
public IoBuffer p1neg (int value) {
buf.put ((byte) -value);
return this;
}
public IoBuffer p2 (int value) {
buf.put((byte) (value >> 8));
buf.put((byte) value);
return this;
}
public IoBuffer p2add (int value) {
buf.put((byte) (value >> 8));
buf.put((byte) (value + 128));
return this;
}
public IoBuffer ip2 (int value) {
buf.put((byte) value);
buf.put((byte) (value >> 8));
return this;
}
public IoBuffer ip2add (int value) {
buf.put((byte) (value + 128));
buf.put((byte) (value >> 8));
return this;
}
public IoBuffer p3 (int value) {
buf.put((byte) (value >> 16));
buf.put((byte) (value >> 8));
buf.put((byte) value);
return this;
}
public IoBuffer ip3 (int value) {
buf.put((byte) value);
buf.put((byte) (value >> 8));
buf.put((byte) (value >> 16));
return this;
}
public IoBuffer p4 (int value) {
buf.put((byte) (value >> 24));
buf.put((byte) (value >> 16));
buf.put((byte) (value >> 8));
buf.put((byte) value);
return this;
}
public IoBuffer ip4 (int value) {
buf.put((byte) value);
buf.put((byte) (value >> 8));
buf.put((byte) (value >> 16));
buf.put((byte) (value >> 24));
return this;
}
public IoBuffer mp4 (int value) {
buf.put((byte) (value >> 16));
buf.put((byte) (value >> 24));
buf.put((byte) value);
buf.put((byte) (value >> 8));
return this;
}
public IoBuffer imp4 (int value) {
buf.put((byte) (value >> 8));
buf.put((byte) value);
buf.put((byte) (value >> 24));
buf.put((byte) (value >> 16));
return this;
}
public IoBuffer p8 (long value) {
buf.put((byte) (value >> 56));
buf.put((byte) (value >> 48));
buf.put((byte) (value >> 40));
buf.put((byte) (value >> 32));
buf.put((byte) (value >> 24));
buf.put((byte) (value >> 16));
buf.put((byte) (value >> 8));
buf.put((byte) value);
return this;
}
public IoBuffer pVarInt (int value) {
if ((value & 0xffffff80) != 0) {
if ((value & 0xffffc000) != 0) {
if ((value & 0xFFE00000) != 0) {
if ((value & 0xF0000000) != 0) {
this.p1(value >>> 28 | 0x80);
}
this.p1(value >>> 21 | 0x80);
}
this.p1(value >>> 14 | 0x80);
}
this.p1(value >>> 7 | 0x80);
}
return this.p1(value & 0x7F);
}
public IoBuffer pVarLong (int size, long value) {
int bytes = size - 1;
if (bytes < 0 || bytes > 7)
throw new IllegalArgumentException();
for (int shift = bytes * 8; shift >= 0; shift -= 8)
this.p1 ((byte) (value >> shift));
return this;
}
public IoBuffer psmarts (int value) {
if (value >= 0 && value < 128)
this.p1(value);
else if (value >= 0 && value < 0x8000)
this.p2(value + 0x8000);
else
throw new IllegalArgumentException("smart out of range: $value");
return this;
}
public IoBuffer psize (int length) {
buf.put (buf.position() - length - 1, (byte) length);
return this;
}
public IoBuffer psizeadd (int length) {
buf.put (buf.position() - length - 1, (byte) (length + 128));
return this;
}
public int g1() {
return buf.get() & 0xFF;
}
public int g1add() {
return (buf.get() - 128) & 0xFF;
}
public int g1neg() {
return -(buf.get() & 0xFF);
}
public int g1sub() {
return (128 - buf.get()) & 0xFF;
}
public int g2() {
return ((buf.get() & 0xFF) << 8) + (buf.get() & 0xFF);
}
public int g2add() {
return ((buf.get() & 0xff) << 8) + ((buf.get() - 128) & 0xFF);
}
public int ig2() {
return (buf.get() & 0xFF) + ((buf.get() & 0xFF) << 8);
}
public int ig2add() {
return ((buf.get() - 128) & 0xFF) + ((buf.get() & 0xFF) << 8);
}
public int g3() {
return ((buf.get() & 0xFF) << 16) + ((buf.get() & 0xFF) << 8) + (buf.get() & 0xFF);
}
public int ig3() {
return (buf.get() & 0xFF) + ((buf.get() & 0xFF) << 8) + ((buf.get() & 0xFF) << 16);
}
public int g4() {
return ((buf.get() & 0xFF) << 24) + ((buf.get() & 0xFF) << 16) + ((buf.get() & 0xFF) << 8) + (buf.get() & 0xFF);
}
public int ig4() {
return (buf.get() & 0xFF) + ((buf.get() & 0xFF) << 8) + ((buf.get() & 0xFF) << 16) + ((buf.get() & 0xFF) << 24);
}
public int m4() {
return ((buf.get() & 0xFF) << 16) + ((buf.get() & 0xFF) << 24) + (buf.get() & 0xFF) + ((buf.get() & 0xFF) << 8);
}
public int im4() {
return ((buf.get() & 0xFF) << 8) + (buf.get() & 0xFF) + ((buf.get() & 0xFF) << 24) + ((buf.get() & 0xFF) << 16);
}
public long g8() {
long low = (long) this.g4() & 0xFFFFFFFFL;
long high = (long) this.g4() & 0xFFFFFFFFL;
return high + (low << 32);
}
/**
* @param val
* @return
@ -635,4 +848,4 @@ public class IoBuffer {
return bitPosition;
}
}
}

View file

@ -25,7 +25,7 @@ import core.game.system.task.Pulse
import core.game.world.map.Location
import core.game.world.map.RegionManager
import core.game.world.update.flag.context.ChatMessage
import core.game.world.update.flag.player.ChatFlag
import core.game.world.update.flag.*
import core.net.amsc.MSPacketRepository
import core.net.packet.context.PlayerContext
import core.net.packet.out.ClearMinimapFlag
@ -229,7 +229,7 @@ object PacketProcessor {
}
PlayerMonitor.logChat(pkt.player, "public", pkt.message)
val ctx = ChatMessage(pkt.player, pkt.message, pkt.effects, pkt.message.length)
pkt.player.updateMasks.register(ChatFlag(ctx))
pkt.player.updateMasks.register(EntityFlag.Chat, ctx)
}
}
is Packet.ChatSetting -> {

View file

@ -11,7 +11,7 @@ import core.game.node.entity.player.Player
import core.game.node.entity.skill.Skills
import core.game.system.task.Pulse
import core.game.world.update.flag.context.ChatMessage
import core.game.world.update.flag.player.ChatFlag
import core.game.world.update.flag.*
import proto.management.ClanMessage
import core.game.world.GameWorld.Pulser
import core.net.packet.`in`.QCPacketType
@ -54,7 +54,7 @@ object QCRepository {
ctx.isQuickChat = true
Pulser.submit(object : Pulse(0, player) {
override fun pulse(): Boolean {
player.updateMasks.register(ChatFlag(ctx))
player.updateMasks.register(EntityFlag.Chat, ctx)
return true
}
})

View file

@ -2,6 +2,7 @@ package core.tools
const val tick = 600 //ms
const val second = 1000 //ms
const val cycle = 20 //ms
fun secondsToTicks(seconds: Int): Int {
val seconds = seconds * second //seconds -> ms
@ -13,6 +14,11 @@ fun ticksToSeconds(ticks: Int): Int {
return ticksMs / 1000
}
fun cyclesToTicks (cycles: Int) : Int {
val cyclesPerTick = tick / cycle
return kotlin.math.ceil (cycles / cyclesPerTick.toDouble()).toInt()
}
fun minutesToTicks(minutes: Int): Int {
val minutesMs = minutes * 60 * 1000
return minutesMs / tick
@ -24,4 +30,4 @@ fun ticksToMinutes(ticks: Int): Int {
}
const val ticksPerSecond = second / tick
const val ticksPerMinute = 60 * ticksPerSecond
const val ticksPerMinute = 60 * ticksPerSecond