#include "AppHdr.h" #include "cluautil.h" #include "l_libs.h" #include "areas.h" #include "database.h" #include "delay.h" #include "dlua.h" #include "libutil.h" #include "mon-act.h" #include "mon-behv.h" #include "mon-death.h" #include "mon-util.h" #include "mon-speak.h" #include "monster.h" #include "strings.h" #define WRAPPED_MONSTER(ls, name) \ MonsterWrap *___mw = clua_get_userdata< MonsterWrap >(ls, MONS_METATABLE); \ if (!___mw \ || !___mw->mons \ || CLua::get_vm(ls).managed_vm && ___mw->turn != you.num_turns) \ { \ luaL_argerror(ls, 1, "Invalid monster wrapper"); \ } \ monster *name(___mw->mons) ///////////////////////////////////////////////////////////////////// // Monster handling void push_monster(lua_State *ls, monster* mons) { MonsterWrap *mw = clua_new_userdata< MonsterWrap >(ls, MONS_METATABLE); mw->turn = you.num_turns; mw->mons = mons; } #define MDEF(name) \ static int l_mons_##name(lua_State *ls, monster* mons, \ const char *attr) \ #define MDEFN(name, closure) \ static int l_mons_##name(lua_State *ls, monster* mons, const char *attrs) \ { \ lua_pushlightuserdata(ls, mons); \ lua_pushcclosure(ls, l_mons_##closure, 1); \ return 1; \ } MDEF(name) { PLUARET(string, mons->name(DESC_PLAIN, true).c_str()); } MDEF(unique) { PLUARET(boolean, mons_is_unique(mons->type)); } MDEF(base_name) { PLUARET(string, mons->base_name(DESC_PLAIN, true).c_str()); } MDEF(full_name) { PLUARET(string, mons->full_name(DESC_PLAIN, true).c_str()); } MDEF(db_name) { PLUARET(string, mons->name(DESC_DBNAME, true).c_str()); } MDEF(type_name) { PLUARET(string, mons_type_name(mons->type, DESC_PLAIN).c_str()); } MDEF(entry_name) { ASSERT_DLUA; const monsterentry *me = get_monster_data(mons->type); if (me) lua_pushstring(ls, me->name); else lua_pushnil(ls); return 1; } MDEF(x) { PLUARET(number, int(mons->pos().x) - int(you.pos().x)); } MDEF(y) { PLUARET(number, int(mons->pos().y) - int(you.pos().y)); } MDEF(hp) { PLUARET(number, int(mons->hit_points)); } static const char* _behaviour_name(beh_type beh); MDEF(beh) { PLUARET(string, _behaviour_name(mons->behaviour)); } MDEF(energy) { // XXX: fix this after speed_increment clean up PLUARET(number, (mons->speed_increment - 79)) } LUAFN(l_mons_add_energy) { ASSERT_DLUA; monster* mons = clua_get_lightuserdata(ls, lua_upvalueindex(1)); mons->speed_increment += luaL_checkint(ls, 1); return 0; } MDEFN(add_energy, add_energy) #define LUANAMEFN(name, expr) \ static int l_mons_##name##_fn(lua_State *ls) { \ ASSERT_DLUA; \ const monster* mons = \ clua_get_lightuserdata(ls, lua_upvalueindex(1)); \ const description_level_type dtype = \ lua_isstring(ls, 1)? \ description_type_by_name(luaL_checkstring(ls, 1)) \ : DESC_PLAIN; \ PLUARET(string, expr.c_str()); \ } \ MDEFN(name, name##_fn) \ LUANAMEFN(mname, mons->name(dtype, true)) LUANAMEFN(mfull_name, mons->full_name(dtype, true)) LUANAMEFN(mbase_name, mons->base_name(dtype, true)) MDEF(hd) { PLUARET(number, mons->get_hit_dice()); } MDEF(targetx) { PLUARET(number, mons->target.x); } MDEF(targety) { PLUARET(number, mons->target.y); } MDEF(shapeshifter) { ASSERT_DLUA; if (mons->has_ench(ENCH_GLOWING_SHAPESHIFTER)) lua_pushstring(ls, "glowing shapeshifter"); else if (mons->has_ench(ENCH_SHAPESHIFTER)) lua_pushstring(ls, "shapeshifter"); else lua_pushnil(ls); return 1; } MDEF(dancing_weapon) { ASSERT_DLUA; if (mons_genus(mons->type) == MONS_DANCING_WEAPON) lua_pushstring(ls, "dancing weapon"); else lua_pushnil(ls); return 1; } MDEF(wont_attack) { ASSERT_DLUA; lua_pushboolean(ls, mons->wont_attack()); return 1; } static const char *_monuse_names[] = { "nothing", "open_doors", "starting_equipment", "weapons_armour" }; static const char *_monuse_to_str(mon_itemuse_type utyp) { COMPILE_CHECK(ARRAYSZ(_monuse_names) == NUM_MONUSE); return _monuse_names[utyp]; } MDEF(muse) { if (const monsterentry *me = mons->find_monsterentry()) PLUARET(string, _monuse_to_str(me->gmon_use)); return 0; } static const char *_moneat_names[] = { "nothing", "items", "corpses", #if TAG_MAJOR_VERSION == 34 "food", #endif }; static const char *_moneat_to_str(mon_itemeat_type etyp) { COMPILE_CHECK(ARRAYSZ(_moneat_names) == NUM_MONEAT); return _moneat_names[etyp]; } MDEF(meat) { if (const monsterentry *me = mons->find_monsterentry()) PLUARET(string, _moneat_to_str(me->gmon_eat)); return 0; } static int l_mons_do_dismiss(lua_State *ls) { // dismiss is only callable from dlua, not from managed VMs (i.e. // end-user scripts cannot dismiss monsters). ASSERT_DLUA; monster* mons = clua_get_lightuserdata(ls, lua_upvalueindex(1)); if (mons->alive()) { mons->flags |= MF_HARD_RESET; monster_die(mons, KILL_DISMISSED, NON_MONSTER); } return 0; } MDEFN(dismiss, do_dismiss) static int l_mons_set_hp(lua_State *ls) { ASSERT_DLUA; monster* mons = clua_get_lightuserdata(ls, lua_upvalueindex(1)); int hp = luaL_checkint(ls, 1); if (hp <= 0) { luaL_argerror(ls, 1, "hp must be positive"); return 0; } hp = min(hp, mons->max_hit_points); mons->hit_points = min(hp, MAX_MONSTER_HP); return 0; } MDEFN(set_hp, set_hp) static int l_mons_set_max_hp(lua_State *ls) { ASSERT_DLUA; monster* mons = clua_get_lightuserdata(ls, lua_upvalueindex(1)); int maxhp = luaL_checkint(ls, 1); if (maxhp <= 0) { luaL_argerror(ls, 1, "maxhp must be positive"); return 0; } mons->max_hit_points = min(maxhp, MAX_MONSTER_HP); mons->hit_points = mons->max_hit_points; return 0; } MDEFN(set_max_hp, set_max_hp) // Run the monster AI code. static int l_mons_do_run_ai(lua_State *ls) { ASSERT_DLUA; monster* mons = clua_get_lightuserdata(ls, lua_upvalueindex(1)); if (mons->alive()) handle_monster_move(mons); return 0; } MDEFN(run_ai, do_run_ai) static int l_mons_do_handle_behaviour(lua_State *ls) { ASSERT_DLUA; monster* mons = clua_get_lightuserdata(ls, lua_upvalueindex(1)); if (mons->alive()) handle_behaviour(mons); return 0; } MDEFN(handle_behaviour, do_handle_behaviour) static int l_mons_do_random_teleport(lua_State *ls) { // We should only be able to teleport monsters from dlua. ASSERT_DLUA; monster* mons = clua_get_lightuserdata(ls, lua_upvalueindex(1)); if (mons->alive()) mons->teleport(true); return 0; } MDEFN(random_teleport, do_random_teleport) MDEF(experience) { ASSERT_DLUA; PLUARET(number, exper_value(mons)); } static int l_mons_do_set_prop(lua_State *ls) { // We should only be able to set properties from dlua. ASSERT_DLUA; monster* mons = clua_get_lightuserdata(ls, lua_upvalueindex(1)); const char *prop_name = luaL_checkstring(ls, 1); if (lua_isnoneornil(ls, 2)) mons->props.erase(prop_name); else if (lua_isboolean(ls, 2)) mons->props[prop_name] = (bool) lua_toboolean(ls, 2); // NOTE: number has to be before string, or numbers will get converted // into strings. else if (lua_isnumber(ls, 2)) mons->props[prop_name].get_int() = luaL_checklong(ls, 2); else if (lua_isstring(ls, 2)) mons->props[prop_name] = lua_tostring(ls, 2); else if (lua_isfunction(ls, 2)) { dlua_chunk chunk(ls); mons->props[prop_name] = chunk; } else { string err = make_stringf("Don't know how to set monster property of the " "given value type for property '%s'", prop_name); luaL_argerror(ls, 2, err.c_str()); } return 0; } MDEFN(set_prop, do_set_prop) static int l_mons_do_get_prop(lua_State *ls) { // We should only be able to set properties from dlua. ASSERT_DLUA; monster* mons = clua_get_lightuserdata(ls, lua_upvalueindex(1)); const char *prop_name = luaL_checkstring(ls, 1); if (!mons->props.exists(prop_name)) { if (lua_isboolean(ls, 2)) { string err = make_stringf("Don't have a property called '%s'.", prop_name); luaL_argerror(ls, 2, err.c_str()); } return 0; } CrawlStoreValue prop = mons->props[prop_name]; int num_pushed = 1; switch (prop.get_type()) { case SV_NONE: lua_pushnil(ls); break; case SV_BOOL: lua_pushboolean(ls, prop.get_bool()); break; case SV_BYTE: lua_pushboolean(ls, prop.get_byte()); break; case SV_SHORT: lua_pushnumber(ls, prop.get_short()); break; case SV_INT: lua_pushnumber(ls, prop.get_int()); break; case SV_FLOAT: lua_pushnumber(ls, prop.get_float()); break; case SV_STR: lua_pushstring(ls, prop.get_string().c_str()); break; case SV_COORD: num_pushed++; lua_pushnumber(ls, prop.get_coord().x); lua_pushnumber(ls, prop.get_coord().y); break; default: // Do nothing for some things. lua_pushnil(ls); break; } return num_pushed; } MDEFN(get_prop, do_get_prop) static int l_mons_do_has_prop(lua_State *ls) { // We should only be able to get properties from dlua. ASSERT_DLUA; monster* mons = clua_get_lightuserdata(ls, lua_upvalueindex(1)); const char *prop_name = luaL_checkstring(ls, 1); lua_pushboolean(ls, mons->props.exists(prop_name)); return 1; } MDEFN(has_prop, do_has_prop) static int l_mons_do_add_ench(lua_State *ls) { ASSERT_DLUA; monster* mons = clua_get_lightuserdata(ls, lua_upvalueindex(1)); const char *ench_name = luaL_checkstring(ls, 1); enchant_type met = name_to_ench(ench_name); if (!met) { string err = make_stringf("No such enchantment: %s", ench_name); luaL_argerror(ls, 1, err.c_str()); return 0; } mons->add_ench(mon_enchant(met, luaL_checkint(ls, 2), 0, luaL_checkint(ls, 3))); return 0; } MDEFN(add_ench, do_add_ench) static int l_mons_do_del_ench(lua_State *ls) { ASSERT_DLUA; monster* mons = clua_get_lightuserdata(ls, lua_upvalueindex(1)); const char *ench_name = luaL_checkstring(ls, 1); enchant_type met = name_to_ench(ench_name); if (!met) { string err = make_stringf("No such enchantment: %s", ench_name); luaL_argerror(ls, 1, err.c_str()); return 0; } mons->del_ench(met); return 0; } MDEFN(del_ench, do_del_ench) MDEF(you_can_see) { ASSERT_DLUA; PLUARET(boolean, you.can_see(mons)); } static int l_mons_do_speak(lua_State *ls) { // We should only be able to get monsters to speak from dlua. ASSERT_DLUA; monster* mons = clua_get_lightuserdata(ls, lua_upvalueindex(1)); const char *speech_key = luaL_checkstring(ls, 1); mons_speaks_msg(mons, getSpeakString(speech_key), MSGCH_TALK, silenced(mons->pos())); return 0; } MDEFN(speak, do_speak) struct MonsAccessor { const char *attribute; int (*accessor)(lua_State *ls, monster* mons, const char *attr); }; static MonsAccessor mons_attrs[] = { { "name", l_mons_name }, { "base_name", l_mons_base_name }, { "full_name", l_mons_full_name }, { "db_name", l_mons_db_name }, { "type_name", l_mons_type_name }, { "entry_name", l_mons_entry_name }, { "unique" , l_mons_unique }, { "shapeshifter", l_mons_shapeshifter }, { "dancing_weapon", l_mons_dancing_weapon }, { "wont_attack", l_mons_wont_attack }, { "x" , l_mons_x }, { "y" , l_mons_y }, { "hd" , l_mons_hd }, { "beh" , l_mons_beh }, { "muse", l_mons_muse }, { "meat", l_mons_meat }, { "hp" , l_mons_hp }, { "targetx", l_mons_targetx }, { "targety", l_mons_targety }, { "mname", l_mons_mname }, { "mfull_name", l_mons_mfull_name }, { "mbase_name", l_mons_mbase_name }, { "energy", l_mons_energy }, { "add_energy", l_mons_add_energy }, { "dismiss", l_mons_dismiss }, { "set_hp", l_mons_set_hp }, { "set_max_hp", l_mons_set_max_hp }, { "run_ai", l_mons_run_ai }, { "handle_behaviour",l_mons_handle_behaviour }, { "experience", l_mons_experience }, { "random_teleport", l_mons_random_teleport }, { "set_prop", l_mons_set_prop }, { "get_prop", l_mons_get_prop }, { "has_prop", l_mons_has_prop }, { "add_ench", l_mons_add_ench }, { "del_ench", l_mons_del_ench }, { "you_can_see", l_mons_you_can_see }, { "speak", l_mons_speak } }; static int monster_get(lua_State *ls) { WRAPPED_MONSTER(ls, mons); const char *attr = luaL_checkstring(ls, 2); if (!attr) return 0; for (unsigned i = 0; i < ARRAYSZ(mons_attrs); ++i) if (!strcmp(attr, mons_attrs[i].attribute)) return mons_attrs[i].accessor(ls, mons, attr); return 0; } static const char *_monster_behaviour_names[] = { "sleep", "wander", "seek", "flee", "cornered", #if TAG_MAJOR_VERSION == 34 "panic", #endif "lurk", "retreat", "withdraw", }; static const char* _behaviour_name(beh_type beh) { if (0 <= beh && beh < NUM_BEHAVIOURS) return _monster_behaviour_names[beh]; else return "invalid"; } static beh_type behaviour_by_name(const string &name) { COMPILE_CHECK(ARRAYSZ(_monster_behaviour_names) == NUM_BEHAVIOURS); for (unsigned i = 0; i < NUM_BEHAVIOURS; ++i) if (name == _monster_behaviour_names[i]) return static_cast(i); return NUM_BEHAVIOURS; } static int monster_set(lua_State *ls) { // Changing monster behaviour is for the dungeon builder only, // never for user scripts. ASSERT_DLUA; MonsterWrap *mw = clua_get_userdata< MonsterWrap >(ls, MONS_METATABLE); if (!mw || !mw->mons) return 0; const char *attr = luaL_checkstring(ls, 2); if (!attr) return 0; if (!strcmp(attr, "beh")) { const beh_type beh = lua_isnumber(ls, 3) ? static_cast(luaL_checkint(ls, 3)) : lua_isstring(ls, 3) ? behaviour_by_name(lua_tostring(ls, 3)) : NUM_BEHAVIOURS; if (beh != NUM_BEHAVIOURS) mw->mons->behaviour = beh; } else if (!strcmp(attr, "targetx")) mw->mons->target.x = luaL_checkint(ls, 3); else if (!strcmp(attr, "targety")) mw->mons->target.y = luaL_checkint(ls, 3); return 0; } static int mons_behaviour(lua_State *ls) { if (lua_gettop(ls) < 1) return 0; if (lua_isnumber(ls, 1)) { lua_pushvalue(ls, 1); return 1; } else if (lua_isstring(ls, 1)) { const beh_type beh = behaviour_by_name(lua_tostring(ls, 1)); if (beh != NUM_BEHAVIOURS) { lua_pushnumber(ls, beh); return 1; } } return 0; } static const struct luaL_reg mons_lib[] = { { "behaviour", mons_behaviour }, { NULL, NULL } }; void dluaopen_monsters(lua_State *ls) { lua_stack_cleaner stack_clean(ls); luaL_newmetatable(ls, MONS_METATABLE); lua_pushstring(ls, "__index"); lua_pushcfunction(ls, monster_get); lua_settable(ls, -3); lua_pushstring(ls, "__newindex"); lua_pushcfunction(ls, monster_set); lua_settable(ls, -3); luaL_register(ls, "mons", mons_lib); }