Rewrote impling system
Fixed static impling spawns Fixed impling spawn ratio Reworked Puro-Puro, much more authentic Imp defenders now work properly Crop circles now appear Added admin command ::implings to list active implings (excludes implings from Puro-Puro) Fixed bug where you could add to a finished compost bin
This commit is contained in:
@@ -427,6 +427,28 @@ fun replaceScenery(toReplace: Scenery, with: Int, forTicks: Int, loc: Location?
|
||||
toReplace.isActive = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a scenery to the world
|
||||
* @param sceneryId the ID of the scenery to add
|
||||
* @param location the location to place it at
|
||||
* @param rotation the rotation of the scenery (default 0)
|
||||
* @param type the type of the scenery (default 22)
|
||||
* @return the created scenery
|
||||
*/
|
||||
fun addScenery (sceneryId: Int, location: Location, rotation: Int = 0, type: Int = 22) : Scenery {
|
||||
val scenery = Scenery(sceneryId, location, type, rotation)
|
||||
SceneryBuilder.add(scenery)
|
||||
return scenery
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a scenery from the world
|
||||
* @param scenery the Scenery object to remove.
|
||||
*/
|
||||
fun removeScenery (scenery: Scenery) {
|
||||
SceneryBuilder.remove(scenery)
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace an object with the given revert timer with the given rotation
|
||||
* @param toReplace the Scenery instance we are replacing
|
||||
@@ -642,6 +664,15 @@ fun face(entity: Entity, toFace: Node, duration: Int = -1) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes the given entity to reset its face direction.
|
||||
* @param entity the entity to reset.
|
||||
*/
|
||||
fun resetFace (entity: Entity) {
|
||||
entity.face(null)
|
||||
entity.faceLocation(entity.location)
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the given interface for the given player
|
||||
* @param player the player to open the interface for
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package core.api.utils
|
||||
|
||||
import core.game.world.map.Location
|
||||
import kotlin.math.*
|
||||
|
||||
class Vector (val x: Double, val y: Double) {
|
||||
fun normalized() : Vector {
|
||||
val magnitude = magnitude()
|
||||
val xComponent = x / magnitude
|
||||
val yComponent = y / magnitude
|
||||
return Vector(xComponent, yComponent)
|
||||
}
|
||||
|
||||
fun magnitude() : Double {
|
||||
return sqrt(x.pow(2.0) + y.pow(2.0))
|
||||
}
|
||||
|
||||
operator fun Vector.unaryMinus() = Vector(-x, -y)
|
||||
|
||||
operator fun times (other: Double) : Vector {
|
||||
return Vector(this.x * other, this.y * other)
|
||||
}
|
||||
|
||||
operator fun times (other: Int) : Vector {
|
||||
return Vector(this.x * other, this.y * other)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun betweenLocs (from: Location, to: Location) : Vector {
|
||||
val xDiff = to.x - from.x
|
||||
val yDiff = to.y - from.y
|
||||
return Vector (xDiff.toDouble(), yDiff.toDouble())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package core.api.utils
|
||||
|
||||
import core.tools.*
|
||||
|
||||
/**
|
||||
* Implementation of a weighted table that supports generic (particularly non-Item) types.
|
||||
**/
|
||||
class WeightedTable<T> : ArrayList<Pair<T?, Double>>() {
|
||||
var totalWeight: Double = 0.0
|
||||
|
||||
fun add (element: T?, weight: Double) : Boolean {
|
||||
totalWeight += weight
|
||||
return super.add(Pair(element, weight))
|
||||
}
|
||||
|
||||
fun remove (element: T?) : Boolean {
|
||||
var index = -1
|
||||
for ((i, pair) in this.withIndex()) {
|
||||
val (elem, _) = pair
|
||||
if (element == elem) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if (index == -1) return false
|
||||
|
||||
this.removeAt(index)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun removeAt (index: Int) : Pair<T?, Double> {
|
||||
val (_, weight) = this[index]
|
||||
totalWeight -= weight
|
||||
return super.removeAt(index)
|
||||
}
|
||||
|
||||
fun roll() : T? {
|
||||
if (this.size == 1) return this[0].component1()
|
||||
else if (this.size == 0) return null
|
||||
|
||||
var shuffled = this.shuffled()
|
||||
var randWeight = RandomFunction.random(0.0, totalWeight)
|
||||
|
||||
for ((element, weight) in shuffled) {
|
||||
randWeight -= weight
|
||||
if (randWeight <= 0)
|
||||
return element
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun <T> create (vararg elements: Pair<T?, Double>) : WeightedTable<T> {
|
||||
var table = WeightedTable<T>()
|
||||
for ((element, weight) in elements)
|
||||
table.add (element, weight)
|
||||
return table
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import core.game.world.update.flag.npc.NPCForceChat;
|
||||
import core.game.world.update.flag.npc.NPCSwitchId;
|
||||
import core.tools.RandomFunction;
|
||||
import core.api.utils.GlobalKillCounter;
|
||||
import core.api.utils.Vector;
|
||||
import core.game.shops.Shops;
|
||||
import core.game.node.entity.combat.CombatSwingHandler;
|
||||
import core.game.system.config.NPCConfigParser;
|
||||
@@ -142,7 +143,7 @@ public class NPC extends Entity {
|
||||
*/
|
||||
private String forceTalk;
|
||||
|
||||
public final NPCBehavior behavior;
|
||||
public NPCBehavior behavior;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code NPC} {@code Object}.
|
||||
@@ -373,7 +374,7 @@ public class NPC extends Entity {
|
||||
|
||||
@Override
|
||||
public boolean isAttackable(Entity entity, CombatStyle style, boolean message) {
|
||||
if ((entity instanceof Player && !definition.hasAction("attack")) || isInvisible()) {
|
||||
if (isInvisible()) {
|
||||
return false;
|
||||
}
|
||||
if (task != null && entity instanceof Player && task.levelReq > entity.getSkills().getStaticLevel(Skills.SLAYER)) {
|
||||
@@ -719,15 +720,10 @@ public class NPC extends Entity {
|
||||
Location returnToSpawnLocation = getProperties().getSpawnLocation().transform(-5 + RandomFunction.random(getWalkRadius()), -5 + RandomFunction.random(getWalkRadius()), 0);
|
||||
int dist = (int) Location.getDistance(location, returnToSpawnLocation);
|
||||
int pathLimit = 15;
|
||||
if(dist > pathLimit || dist < -pathLimit)
|
||||
{
|
||||
int diffX = returnToSpawnLocation.getX() - location.getX();
|
||||
int diffY = returnToSpawnLocation.getY() - location.getY();
|
||||
returnToSpawnLocation = location.transform(
|
||||
Math.min(Math.max(-pathLimit, diffX), pathLimit), //Try to path no more than pathLimit and no less than -pathLimit tiles away in the X and Y directions
|
||||
Math.min(Math.max(-pathLimit, diffY), pathLimit),
|
||||
0);
|
||||
}
|
||||
if (dist > pathLimit) {
|
||||
Vector normalizedDir = Vector.betweenLocs(this.location, returnToSpawnLocation).normalized();
|
||||
returnToSpawnLocation = this.location.transform (normalizedDir.times(pathLimit));
|
||||
}
|
||||
return returnToSpawnLocation;
|
||||
}
|
||||
Location l = movementPath[movementIndex++];
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package core.game.node.entity.npc
|
||||
|
||||
import core.game.node.item.Item
|
||||
import core.api.ContentInterface
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.world.map.RegionManager
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.entity.combat.BattleState
|
||||
import core.game.node.entity.combat.CombatStyle
|
||||
import core.game.node.entity.combat.CombatSwingHandler
|
||||
import core.game.node.item.Item
|
||||
import core.game.world.map.path.ClipMaskSupplier
|
||||
|
||||
open class NPCBehavior(vararg val ids: Int = intArrayOf()) : ContentInterface {
|
||||
companion object {
|
||||
@@ -19,6 +22,12 @@ open class NPCBehavior(vararg val ids: Int = intArrayOf()) : ContentInterface {
|
||||
ids.forEach { idMap[it] = behavior }
|
||||
}
|
||||
}
|
||||
|
||||
object StandardClipMaskSupplier : ClipMaskSupplier {
|
||||
override fun getClippingFlag (z: Int, x: Int, y: Int) : Int {
|
||||
return RegionManager.getClippingFlag(z,x,y)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called every tick, before the base NPC tick() method.
|
||||
@@ -102,7 +111,11 @@ open class NPCBehavior(vararg val ids: Int = intArrayOf()) : ContentInterface {
|
||||
* @param shouldSendMessage whether the core combat code believes you should send a message e.g. "You can't attack this NPC with that weapon"
|
||||
* @return whether the attacker should be able to attack this NPC.
|
||||
*/
|
||||
open fun canBeAttackedBy(self: NPC, attacker: Entity, style: CombatStyle, shouldSendMessage: Boolean) : Boolean {return true}
|
||||
open fun canBeAttackedBy(self: NPC, attacker: Entity, style: CombatStyle, shouldSendMessage: Boolean) : Boolean {
|
||||
if (attacker is Player && !self.definition.hasAction("attack"))
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by combat-related code to check if this NPC should ignore multi-combat rules when attempting to attack the given victim.
|
||||
@@ -119,4 +132,11 @@ open class NPCBehavior(vararg val ids: Int = intArrayOf()) : ContentInterface {
|
||||
* @return the SwingHandler instance to be used for this cycle of combat
|
||||
*/
|
||||
open fun getSwingHandlerOverride(self: NPC, original: CombatSwingHandler) : CombatSwingHandler {return original}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by pathfinding code to determine the clipping mask supplier this NPC should use. You can use this to ignore water, etc.
|
||||
*/
|
||||
open fun getClippingSupplier(self: NPC) : ClipMaskSupplier? {
|
||||
return StandardClipMaskSupplier
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import core.game.node.entity.player.link.IronmanMode
|
||||
import core.game.node.entity.player.link.SpellBookManager
|
||||
import core.game.node.entity.player.link.emote.Emotes
|
||||
import core.game.node.entity.player.link.music.MusicEntry
|
||||
import core.game.world.map.Location
|
||||
import org.json.simple.JSONArray
|
||||
import org.json.simple.JSONObject
|
||||
import org.json.simple.parser.JSONParser
|
||||
@@ -122,6 +123,7 @@ class PlayerSaveParser(val player: Player) {
|
||||
"long" -> attr["value"].toString().toLong()
|
||||
"bool" -> attr["value"] as Boolean
|
||||
"byte" -> Base64.getDecoder().decode(attr["value"].toString())[0]
|
||||
"location" -> Location.fromString(attr["value"].toString())
|
||||
else -> null.also { log(this::class.java, Log.WARN, "Invalid data type for key: $key in PlayerSaveParser.kt Line 115") }
|
||||
})
|
||||
}
|
||||
@@ -363,4 +365,4 @@ class PlayerSaveParser(val player: Player) {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import core.game.container.Container
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.entity.player.link.IronmanMode
|
||||
import core.game.node.entity.skill.Skills
|
||||
import core.game.world.map.Location
|
||||
import core.tools.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@@ -116,6 +117,7 @@ class PlayerSaver (val player: Player){
|
||||
is Short -> "short"
|
||||
is String -> "str"
|
||||
is Byte -> "byte"
|
||||
is Location -> "location"
|
||||
else -> "null".also { log(this::class.java, Log.WARN, "Invalid attribute type for key: $key in PlayerSaver.kt Line 115") }
|
||||
}
|
||||
attr.put("type",type)
|
||||
|
||||
@@ -6,9 +6,11 @@ import core.game.world.map.path.Path;
|
||||
import core.game.world.map.path.Pathfinder;
|
||||
import core.tools.RandomFunction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import core.api.utils.Vector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.lang.Math;
|
||||
|
||||
/**
|
||||
* Represents a location on the world map.
|
||||
@@ -277,14 +279,14 @@ public final class Location extends Node {
|
||||
public ArrayList<Location> getSurroundingTiles() {
|
||||
ArrayList<Location> locs = new ArrayList<>();
|
||||
|
||||
locs.add(transform(0,1,0)); //N
|
||||
locs.add(transform(1,1,0)); //NE
|
||||
locs.add(transform(1,0,0)); //E
|
||||
locs.add(transform(1,-1,0)); //SE
|
||||
locs.add(transform(0,-1,0)); //S
|
||||
locs.add(transform(-1,-1,0));//SW
|
||||
locs.add(transform(-1,0,0));//W
|
||||
locs.add(transform(0,-1,0)); //S
|
||||
locs.add(transform(1,-1,0)); //SE
|
||||
locs.add(transform(1,0,0)); //E
|
||||
locs.add(transform(1,1,0)); //NE
|
||||
locs.add(transform(0,1,0)); //N
|
||||
locs.add(transform(-1,1,0));//NW
|
||||
locs.add(transform(-1,0,0));//W
|
||||
|
||||
return locs;
|
||||
}
|
||||
@@ -554,4 +556,8 @@ public final class Location extends Node {
|
||||
if (sb.length() == 0) return null;
|
||||
return Direction.valueOf(sb.toString());
|
||||
}
|
||||
|
||||
public Location transform (Vector vector) {
|
||||
return Location.create(this.x + (int) Math.floor(vector.getX()), this.y + (int) Math.floor(vector.getY()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package core.game.world.map.path;
|
||||
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.Entity;
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.node.item.GroundItem;
|
||||
import core.game.node.scenery.Scenery;
|
||||
import core.game.world.map.Direction;
|
||||
@@ -124,7 +125,13 @@ public abstract class Pathfinder {
|
||||
* @return The path.
|
||||
*/
|
||||
public static Path find(Entity mover, Node destination, boolean near, Pathfinder finder) {
|
||||
return find(mover.getLocation(), mover.size(), destination, near, finder, RegionManager::getClippingFlag);
|
||||
ClipMaskSupplier cms = null;
|
||||
if (mover instanceof NPC) {
|
||||
cms = ((NPC) mover).behavior.getClippingSupplier(((NPC) mover));
|
||||
}
|
||||
if (cms == null)
|
||||
cms = RegionManager::getClippingFlag;
|
||||
return find(mover.getLocation(), mover.size(), destination, near, finder, cms);
|
||||
}
|
||||
|
||||
public static Path findWater(Entity mover, Node destination, boolean near, Pathfinder finder){
|
||||
|
||||
Reference in New Issue
Block a user