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:
Ceikry
2023-04-20 12:16:21 +00:00
committed by Ryan
parent 41c970efd4
commit f6096e3020
24 changed files with 915 additions and 457 deletions
+31
View File
@@ -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
+35
View File
@@ -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){