Single method for creating multiple entity types in Lua

This commit is contained in:
Jordan Irwin 2022-01-19 19:15:06 -08:00
parent 944fc1cd46
commit 4c088d354b
2 changed files with 494 additions and 0 deletions

View file

@ -23,6 +23,7 @@ import org.luaj.vm2.LuaFunction;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import games.stendhal.common.Direction;
import games.stendhal.server.core.engine.GameEvent;
import games.stendhal.server.core.engine.SingletonRepository;
import games.stendhal.server.core.engine.StendhalRPZone;
@ -32,6 +33,8 @@ import games.stendhal.server.core.pathfinder.Node;
import games.stendhal.server.core.rp.StendhalRPAction;
import games.stendhal.server.core.rule.EntityManager;
import games.stendhal.server.core.scripting.ScriptInLua.LuaLogger;
import games.stendhal.server.entity.CollisionAction;
import games.stendhal.server.entity.Entity;
import games.stendhal.server.entity.RPEntity;
import games.stendhal.server.entity.creature.Creature;
import games.stendhal.server.entity.creature.RaidCreature;
@ -43,6 +46,7 @@ import games.stendhal.server.entity.mapstuff.sign.Sign;
import games.stendhal.server.entity.npc.ChatAction;
import games.stendhal.server.entity.npc.ChatCondition;
import games.stendhal.server.entity.npc.ConversationStates;
import games.stendhal.server.entity.npc.PassiveNPC;
import games.stendhal.server.entity.npc.SilentNPC;
import games.stendhal.server.entity.npc.SpeakerNPC;
import games.stendhal.server.entity.player.Player;
@ -59,6 +63,7 @@ public class LuaEntityHelper {
private static final LuaConditionHelper conditionHelper = LuaConditionHelper.get();
private static final LuaActionHelper actionHelper = LuaActionHelper.get();
private static final LuaTableHelper tableHelper = LuaTableHelper.get();
private static LuaEntityHelper instance;
@ -77,6 +82,441 @@ public class LuaEntityHelper {
return instance;
}
/**
* Creates a new `Entity` instance.
*
* @param lt
* Entity definition table.
* @return
* New `Entity` instance.
*/
public Entity create(final LuaTable lt) {
lt.checktable();
String e_type = lt.get("type").tojstring();
// default to SilentNPC
if (e_type == null) {
e_type = "silentnpc";
}
switch (e_type.toLowerCase()) {
case "speakernpc":
return buildSpeakerNPC(lt);
case "silentnpc":
return buildSilentNPC(lt);
case "sign":
return buildSign(lt);
case "reader":
return buildReader(lt);
case "shopsign":
return buildShopSign(lt);
default:
logger.error("unknown entity type: " + e_type);
}
return null;
}
/**
* Adds attributes defined in {@link games.stendhal.server.entity.Entity}.
*
* @param ent
* The entity to whom attributes will be added.
* @param lt
* Lua table of attributes.
*/
private void setEntityTraits(final Entity ent, final LuaTable lt) {
final LuaValue l_pos = lt.get("pos");
if (!l_pos.isnil()) {
ent.setPosition(l_pos.get(1).checkint(), l_pos.get(2).checkint());
}
final LuaValue l_desc = lt.get("description");
if (!l_desc.isnil()) {
ent.setDescription(l_desc.checkjstring());
}
final LuaValue l_class = lt.get("class");
if (!l_class.isnil()) {
ent.setEntityClass(l_class.checkjstring());
}
final LuaValue l_subclass = lt.get("subclass");
if (!l_subclass.isnil()) {
ent.setEntitySubclass(l_subclass.checkjstring());
}
final LuaValue l_resistance = lt.get("resistance");
if (!l_resistance.isnil()) {
ent.setResistance(l_resistance.checkint());
}
final LuaValue l_size = lt.get("size");
if (!l_size.isnil()) {
ent.setSize(l_size.get(1).checkint(), l_size.get(2).checkint());
}
final LuaValue l_cursor = lt.get("cursor");
if (!l_cursor.isnil()) {
ent.setCursor(l_cursor.checkjstring());
}
final LuaValue l_visibility = lt.get("visibility");
if (!l_visibility.isnil()) {
ent.setVisibility(l_visibility.checkint());
}
final LuaValue l_menu = lt.get("menu");
if (!l_menu.isnil()) {
ent.setMenu(l_menu.checkjstring());
}
}
/**
* Adds attributes defined in {@link games.stendhal.server.entity.npc.PassiveNPC}.
*
* @param npc
* The entity to whom attributes will be added.
* @param lt
* Lua table of attributes.
*/
private void setNPCTraits(final PassiveNPC npc, final LuaTable lt) {
// *** Entity ***
setEntityTraits(npc, lt);
// *** ActiveEntity ***
final LuaValue l_dir = lt.get("dir");
if (!l_dir.isnil()) {
npc.setDirection((Direction) l_dir.checkuserdata(Direction.class));
}
final LuaValue l_ignoresCollision = lt.get("ignoresCollision");
if (!l_ignoresCollision.isnil()) {
npc.setIgnoresCollision(l_ignoresCollision.checkboolean());
}
// *** GuidedEntity ***
LuaValue l_pos = lt.get("pos"); // Entity.class
final LuaValue l_path = lt.get("path");
// entity starts at first node of path
if (l_pos.isnil()) {
l_pos = l_path.get(1);
}
if (!l_pos.isnil()) {
l_pos.checktable();
npc.setPosition(l_pos.get(1).checkint(), l_pos.get(2).checkint());
}
if (!l_path.isnil()) {
l_path.checktable();
boolean loop = true;
final LuaValue l_loop = l_path.get("loop");
if (!l_loop.isnil()) {
loop = l_loop.checkboolean();
}
npc.setPath(tableToPath((LuaTable) l_path.get("nodes"), loop));
/*
npc.setPath(tableHelper.pairsToNodes(
new FixedPath(l_path.get("nodes").checktable())), loop);
*/
final LuaValue l_retrace = l_path.get("retrace");
if (!l_retrace.isnil() && l_retrace.checkboolean()) {
npc.setRetracePath();
}
final LuaValue l_collisionAction = l_path.get("collisionAction");
if (!l_collisionAction.isnil()) {
npc.setCollisionAction((CollisionAction) l_collisionAction.checkuserdata(CollisionAction.class));
}
// TODO: GuidedEntity.addSuspend()
}
/*
final LuaValue l_randMovement = lt.get("randomMovement");
if (!l_randMovement.isnil()) {
l_randMovement.checktable();
Boolean ret;
final LuaValue l_ret = l_randMovement.get("ret");
if (!l_ret.isnil()) {
ret = l_ret.checkboolean();
}
if (ret != null) {
npc.setMovementRadius(l_randMovement.get("radius").checkint(), ret);
} else {
npc.setMovementRadius(l_randMovement.get("radius").checkint());
}
}
*/
final LuaValue l_speed = lt.get("speed");
if (!l_speed.isnil()) {
npc.setBaseSpeed(l_speed.checkdouble());
}
// *** RPEntity ***
final LuaValue l_basehp = lt.get("basehp");
if (!l_basehp.isnil()) {
npc.setBaseHP(l_basehp.checkint());
}
final LuaValue l_hp = lt.get("hp");
if (!l_hp.isnil()) {
npc.setHP(l_hp.checkint());
}
final LuaValue l_title = lt.get("title");
if (!l_title.isnil()) {
npc.setTitle(l_title.checkjstring());
}
// *** DressedEntity ***
final LuaValue l_outfit = lt.get("outfit");
if (!l_outfit.isnil()) {
npc.setOutfit(l_outfit.get("layers").checkjstring());
final LuaTable l_outfitColors = (LuaTable) l_outfit.get("colors");
if (!l_outfitColors.isnil()) {
l_outfitColors.checktable();
for (final LuaValue l_key: l_outfitColors.keys()) {
npc.setOutfitColor(l_key.checkjstring(), l_outfitColors.get(l_key).checkint());
}
}
}
// *** NPC ***
final LuaValue l_idea = lt.get("idea");
if (!l_idea.isnil()) {
npc.setIdea(l_idea.checkjstring());
}
final LuaValue l_sounds = lt.get("sounds");
if (l_sounds.istable() && l_sounds.length() > 0) {
final List<String> sounds = new ArrayList<>();
for (int idx = 1; idx <= l_sounds.length(); idx++) {
sounds.add(l_sounds.get(idx).checkjstring());
}
npc.setSounds(sounds);
}
// TODO: NPC: setMovement, setRandomPathFrom, setPerceptionRange, setMovementRange
// moveRandomly, setPathCompletedPause
// *** PassiveNPC ***
final LuaValue l_teleports = lt.get("teleports");
if (!l_teleports.isnil()) {
npc.setTeleportsFlag(l_teleports.checkboolean());
}
}
/**
* Create a new interactive NPC.
*
* @param lt
* Lua table of attributes.
* @see
* games.stendhal.server.entity.npc.SpeakerNPC.
*/
private LuaSpeakerNPC buildSpeakerNPC(final LuaTable lt) {
final LuaSpeakerNPC npc = new LuaSpeakerNPC(lt.get("name").checkjstring());
setNPCTraits(npc, lt);
LuaValue l_idleDir = lt.get("idleDir");
if (l_idleDir.isnil()) {
l_idleDir = lt.get("idleDirection");
}
if (!l_idleDir.isnil()) {
npc.setIdleDirection((Direction) l_idleDir.checkuserdata(Direction.class));
}
final LuaValue l_chatTimeout = lt.get("chatTimeout");
if (!l_chatTimeout.isnil()) {
npc.setPlayerChatTimeout(l_chatTimeout.checklong());
}
final LuaValue l_perceptionRange = lt.get("perceptionRange");
if (!l_perceptionRange.isnil()) {
npc.setPerceptionRange(l_perceptionRange.checkint());
}
final LuaValue l_currentState = lt.get("currentState");
if (!l_currentState.isnil()) {
npc.setCurrentState((ConversationStates) l_currentState.checkuserdata(ConversationStates.class));
}
final LuaValue l_greeting = lt.get("greeting");
if (!l_greeting.isnil()) {
l_greeting.checktable();
final String greetText = l_greeting.get("text").checkjstring();
final LuaValue l_greetAction = l_greeting.get("action");
if (!l_greetAction.isnil()) {
npc.addGreeting(greetText, (ChatAction) l_greetAction.checkuserdata(ChatAction.class));
} else {
npc.addGreeting(greetText);
}
}
final LuaValue l_replies = lt.get("replies");
if (!l_replies.isnil()) {
l_replies.checktable();
final LuaValue l_replyQuest = l_replies.get("quest");
if (!l_replyQuest.isnil()) {
npc.addQuest(l_replyQuest.checkjstring());
}
final LuaValue l_replyJob = l_replies.get("job");
if (!l_replyJob.isnil()) {
npc.addJob(l_replyJob.checkjstring());
}
final LuaValue l_replyHelp = l_replies.get("help");
if (!l_replyHelp.isnil()) {
npc.addHelp(l_replyHelp.checkjstring());
}
final LuaValue l_replyOffer = l_replies.get("offer");
if (!l_replyOffer.isnil()) {
npc.addOffer(l_replyOffer.checkjstring());
}
final LuaValue l_replyBye = l_replies.get("bye");
if (!l_replyBye.isnil()) {
npc.addGoodbye(l_replyBye.checkjstring());
}
}
// TODO: addReply
final LuaValue l_altImage = lt.get("alternativeImage");
if (!l_altImage.isnil()) {
npc.setAlternativeImage(l_altImage.checkjstring());
}
return npc;
}
/**
* Creates a new non-interactive NPC.
*
* @param lt
* Lua table of attributes.
* @see
* games.stendhal.server.entity.npc.SilentNPC
*/
private LuaSilentNPC buildSilentNPC(final LuaTable lt) {
final LuaSilentNPC npc = new LuaSilentNPC() {};
setNPCTraits(npc, lt);
return npc;
}
/**
* Creates a new sign.
*
* @param lt
* Lua table of attributes.
* @param visible
* Whether or not the sign should us a sprite visible to the player.
* @see
* games.stendhal.server.entity.mapstuff.sign.Sign
* @see
* games.stendhal.server.entity.mapstuff.sign.Reader
*/
private Sign buildSign(final LuaTable lt, final boolean visible) {
if (!visible) {
return buildReader(lt);
}
final Sign sign = new Sign();
setEntityTraits(sign, lt);
final LuaValue l_text = lt.get("text");
if (!l_text.isnil()) {
sign.setText(l_text.checkjstring());
}
return sign;
}
/**
* Creates a new sign.
*
* @param lt
* Lua table of attributes.
* @see
* {@link #buildSign(LuaTable, boolean)}
*/
private Sign buildSign(final LuaTable lt) {
boolean visible = true;
final LuaValue l_value = lt.get("visible");
if (!l_value.isnil()) {
visible = l_value.toboolean();
}
return buildSign(lt, visible);
}
/**
* Creates a new invisible sign.
*
* @param lt
* Lua table of attributes.
* @see
* games.stendhal.server.entity.mapstuff.sign.Sign
* @see
* games.stendhal.server.entity.mapstuff.sign.Reader
*/
private Reader buildReader(final LuaTable lt) {
final Reader reader = new Reader();
setEntityTraits(reader, lt);
final LuaValue l_text = lt.get("text");
if (!l_text.isnil()) {
reader.setText(l_text.checkjstring());
}
return reader;
}
/**
* Creates a new shop sign.
*
* @param lt
* Lua table of attributes.
* @see
* games.stendhal.server.entity.mapstuff.sign.ShopSign
*/
private ShopSign buildShopSign(final LuaTable lt) {
final ShopSign sign = new ShopSign(
lt.get("name").checkjstring(),
lt.get("title").checkjstring(),
lt.get("caption").checkjstring(),
lt.get("seller").checkboolean());
setEntityTraits(sign, lt);
return sign;
}
/**
* Converts a table of coordinates to a FixedPath instance.
*
@ -195,7 +635,10 @@ public class LuaEntityHelper {
* Name of new NPC.
* @return
* New SpeakerNPC instance.
* @deprecated
* Use {@link #create(LuaTable)}.
*/
@Deprecated
public LuaSpeakerNPC createSpeakerNPC(final String name) {
return new LuaSpeakerNPC(name);
}
@ -205,7 +648,10 @@ public class LuaEntityHelper {
*
* @return
* New SilentNPC instance.
* @deprecated
* Use {@link #create(LuaTable)}.
*/
@Deprecated
public LuaSilentNPC createSilentNPC() {
return new LuaSilentNPC();
}
@ -289,7 +735,10 @@ public class LuaEntityHelper {
*
* @return
* New Sign instance.
* @deprecated
* Use {@link #create(LuaTable)}.
*/
@Deprecated
public Sign createSign() {
return createSign(true);
}
@ -301,7 +750,10 @@ public class LuaEntityHelper {
* If <code>false</code>, sign does not have a visual representation.
* @return
* New Sign instance.
* @deprecated
* Use {@link #create(LuaTable)}.
*/
@Deprecated
public Sign createSign(final boolean visible) {
if (visible) {
return new Sign();
@ -323,7 +775,10 @@ public class LuaEntityHelper {
* <code>true</code>, if this sign is for items sold by an NPC (defaults to <code>true</code> if <code>null</code>).
* @return
* New ShopSign instance.
* @deprecated
* Use {@link #create(LuaTable)}.
*/
@Deprecated
public ShopSign createShopSign(final String name, final String title, final String caption, Boolean seller) {
// default to seller
if (seller == null) {
@ -408,6 +863,10 @@ public class LuaEntityHelper {
/**
* A special interface that overloads setPath & setPathAndPosition
* methods to accept a Lua table as parameter argument.
*
* @todo
* Merge functionality into {@link games.stendhal.server.entity.GuidedEntity}
* & delete this class.
*/
private interface LuaGuidedEntity {
@ -420,6 +879,11 @@ public class LuaEntityHelper {
public void setPathAndPosition(final LuaTable table, Boolean loop);
}
/**
* @todo
* Merge functionality into {@link games.stendhal.server.entity.npc.SpeakerNPC}
* & delete this class.
*/
private class LuaSpeakerNPC extends SpeakerNPC implements LuaGuidedEntity {
private LuaFunction goodbyeAction;
@ -567,6 +1031,11 @@ public class LuaEntityHelper {
}
}
/**
* @todo
* Merge functionality into {@link games.stendhal.server.entity.npc.SilentNPC}
* & delete this class.
*/
private class LuaSilentNPC extends SilentNPC implements LuaGuidedEntity {
@Override

View file

@ -21,6 +21,8 @@ import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
import games.stendhal.server.core.pathfinder.Node;
/**
* Adds some useful function members to Lua "table" object.
@ -96,4 +98,27 @@ public class LuaTableHelper {
}
});
}
/**
* Converts a Lua table pair ({num, num}) to `Node`.
*/
public static Node pairToNode(final LuaTable lt) {
lt.checktable();
return new Node(lt.get(1).checkint(), lt.get(2).checkint());
}
/**
* Converts a Lua table of table pairs ({{int, int}, {int, int}}) to
* list of nodes (`List<Node>`).
*/
public static List<Node> pairsToNodes(final LuaTable lt) {
lt.checktable();
final List<Node> nodes = new LinkedList<>();
for (int idx=1; idx <= lt.length(); idx++) {
nodes.add(pairToNode((LuaTable) lt.get(idx)));
}
return nodes;
}
}