npc.cpp

main 1313 lines · 30.9 KB Raw
Alex Alex Commit Server upload 18/10/2026 23:38
1/**
2 * The Forgotten Server - a free and open-source MMORPG server emulator
3 * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "otpch.h"
21
22#include "npc.h"
23#include "game.h"
24#include "pugicast.h"
25
26extern Game g_game;
27extern LuaEnvironment g_luaEnvironment;
28
29uint32_t Npc::npcAutoID = 0x80000000;
30NpcScriptInterface* Npc::scriptInterface = nullptr;
31
32void Npcs::reload()
33{
34 const std::map<uint32_t, Npc*>& npcs = g_game.getNpcs();
35 for (const auto& it : npcs) {
36 it.second->closeAllShopWindows();
37 }
38
39 delete Npc::scriptInterface;
40 Npc::scriptInterface = nullptr;
41
42 for (const auto& it : npcs) {
43 it.second->reload();
44 }
45}
46
47Npc* Npc::createNpc(const std::string& name)
48{
49 std::unique_ptr<Npc> npc(new Npc(name));
50 if (!npc->load()) {
51 return nullptr;
52 }
53 return npc.release();
54}
55
56Npc::Npc(const std::string& name) :
57 Creature(),
58 filename("data/npc/" + name + ".xml"),
59 npcEventHandler(nullptr),
60 masterRadius(-1),
61 loaded(false)
62{
63 reset();
64}
65
66Npc::~Npc()
67{
68 reset();
69}
70
71void Npc::addList()
72{
73 g_game.addNpc(this);
74}
75
76void Npc::removeList()
77{
78 g_game.removeNpc(this);
79}
80
81bool Npc::load()
82{
83 if (loaded) {
84 return true;
85 }
86
87 reset();
88
89 if (!scriptInterface) {
90 scriptInterface = new NpcScriptInterface();
91 scriptInterface->loadNpcLib("data/npc/lib/npc.lua");
92 }
93
94 loaded = loadFromXml();
95 return loaded;
96}
97
98void Npc::reset()
99{
100 loaded = false;
101 isIdle = true;
102 walkTicks = 1500;
103 pushable = true;
104 floorChange = false;
105 attackable = false;
106 ignoreHeight = true;
107 focusCreature = 0;
108 speechBubble = SPEECHBUBBLE_NONE;
109
110 delete npcEventHandler;
111 npcEventHandler = nullptr;
112
113 parameters.clear();
114 shopPlayerSet.clear();
115}
116
117void Npc::reload()
118{
119 reset();
120 load();
121
122 // Simulate that the creature is placed on the map again.
123 if (npcEventHandler) {
124 npcEventHandler->onCreatureAppear(this);
125 }
126
127 if (walkTicks > 0) {
128 addEventWalk();
129 }
130}
131
132bool Npc::loadFromXml()
133{
134 pugi::xml_document doc;
135 pugi::xml_parse_result result = doc.load_file(filename.c_str());
136 if (!result) {
137 printXMLError("Error - Npc::loadFromXml", filename, result);
138 return false;
139 }
140
141 pugi::xml_node npcNode = doc.child("npc");
142 if (!npcNode) {
143 std::cout << "[Error - Npc::loadFromXml] Missing npc tag in " << filename << std::endl;
144 return false;
145 }
146
147 name = npcNode.attribute("name").as_string();
148 attackable = npcNode.attribute("attackable").as_bool();
149 floorChange = npcNode.attribute("floorchange").as_bool();
150
151 pugi::xml_attribute attr;
152 if ((attr = npcNode.attribute("speed"))) {
153 baseSpeed = pugi::cast<uint32_t>(attr.value());
154 } else {
155 baseSpeed = 100;
156 }
157
158 if ((attr = npcNode.attribute("pushable"))) {
159 pushable = attr.as_bool();
160 }
161
162 if ((attr = npcNode.attribute("walkinterval"))) {
163 walkTicks = pugi::cast<uint32_t>(attr.value());
164 }
165
166 if ((attr = npcNode.attribute("walkradius"))) {
167 masterRadius = pugi::cast<int32_t>(attr.value());
168 }
169
170 if ((attr = npcNode.attribute("ignoreheight"))) {
171 ignoreHeight = attr.as_bool();
172 }
173
174 if ((attr = npcNode.attribute("speechbubble"))) {
175 speechBubble = pugi::cast<uint32_t>(attr.value());
176 }
177
178 if ((attr = npcNode.attribute("skull"))) {
179 setSkull(getSkullType(asLowerCaseString(attr.as_string())));
180 }
181
182 pugi::xml_node healthNode = npcNode.child("health");
183 if (healthNode) {
184 if ((attr = healthNode.attribute("now"))) {
185 health = pugi::cast<int32_t>(attr.value());
186 } else {
187 health = 100;
188 }
189
190 if ((attr = healthNode.attribute("max"))) {
191 healthMax = pugi::cast<int32_t>(attr.value());
192 } else {
193 healthMax = 100;
194 }
195 }
196
197 pugi::xml_node lookNode = npcNode.child("look");
198 if (lookNode) {
199 pugi::xml_attribute lookTypeAttribute = lookNode.attribute("type");
200 if (lookTypeAttribute) {
201 defaultOutfit.lookType = pugi::cast<uint16_t>(lookTypeAttribute.value());
202 defaultOutfit.lookHead = pugi::cast<uint16_t>(lookNode.attribute("head").value());
203 defaultOutfit.lookBody = pugi::cast<uint16_t>(lookNode.attribute("body").value());
204 defaultOutfit.lookLegs = pugi::cast<uint16_t>(lookNode.attribute("legs").value());
205 defaultOutfit.lookFeet = pugi::cast<uint16_t>(lookNode.attribute("feet").value());
206 defaultOutfit.lookAddons = pugi::cast<uint16_t>(lookNode.attribute("addons").value());
207 } else if ((attr = lookNode.attribute("typeex"))) {
208 defaultOutfit.lookTypeEx = pugi::cast<uint16_t>(attr.value());
209 }
210 defaultOutfit.lookMount = pugi::cast<uint16_t>(lookNode.attribute("mount").value());
211 if ((attr = lookNode.attribute("wings"))) {
212 defaultOutfit.lookWings = pugi::cast<uint16_t>(attr.value());
213 }
214 if ((attr = lookNode.attribute("aura"))) {
215 defaultOutfit.lookAura = pugi::cast<uint16_t>(attr.value());
216 }
217 if ((attr = lookNode.attribute("shader"))) {
218 Shader* shader = g_game.shaders.getShaderByName(attr.as_string());
219 defaultOutfit.lookShader = shader ? shader->id : 0;
220 }
221
222 currentOutfit = defaultOutfit;
223 }
224
225 for (auto parameterNode : npcNode.child("parameters").children()) {
226 parameters[parameterNode.attribute("key").as_string()] = parameterNode.attribute("value").as_string();
227 }
228
229 pugi::xml_attribute scriptFile = npcNode.attribute("script");
230 if (scriptFile) {
231 npcEventHandler = new NpcEventsHandler(scriptFile.as_string(), this);
232 if (!npcEventHandler->isLoaded()) {
233 delete npcEventHandler;
234 npcEventHandler = nullptr;
235 return false;
236 }
237 }
238 return true;
239}
240
241bool Npc::canSee(const Position& pos) const
242{
243 if (pos.z != getPosition().z) {
244 return false;
245 }
246 return Creature::canSee(getPosition(), pos, 3, 3);
247}
248
249std::string Npc::getDescription(int32_t) const
250{
251 std::string descr;
252 descr.reserve(name.length() + 1);
253 descr.assign(name);
254 descr.push_back('.');
255 return descr;
256}
257
258void Npc::onCreatureAppear(Creature* creature, bool isLogin)
259{
260 Creature::onCreatureAppear(creature, isLogin);
261
262 if (creature == this) {
263 if (walkTicks > 0) {
264 addEventWalk();
265 }
266
267 if (npcEventHandler) {
268 npcEventHandler->onCreatureAppear(creature);
269 }
270 } else if (Player* player = creature->getPlayer()) {
271 if (npcEventHandler) {
272 npcEventHandler->onCreatureAppear(creature);
273 }
274
275 spectators.insert(player);
276 updateIdleStatus();
277 }
278}
279
280void Npc::onRemoveCreature(Creature* creature, bool isLogout)
281{
282 Creature::onRemoveCreature(creature, isLogout);
283
284 if (creature == this) {
285 closeAllShopWindows();
286 if (npcEventHandler) {
287 npcEventHandler->onCreatureDisappear(creature);
288 }
289 } else if (Player* player = creature->getPlayer()) {
290 if (npcEventHandler) {
291 npcEventHandler->onCreatureDisappear(creature);
292 }
293
294 spectators.erase(player);
295 updateIdleStatus();
296 }
297}
298
299void Npc::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos,
300 const Tile* oldTile, const Position& oldPos, bool teleport)
301{
302 Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport);
303
304 if (creature == this || creature->getPlayer()) {
305 if (npcEventHandler) {
306 npcEventHandler->onCreatureMove(creature, oldPos, newPos);
307 }
308
309 if (creature != this) {
310 Player* player = creature->getPlayer();
311
312 // if player is now in range, add to spectators list, otherwise erase
313 if (player->canSee(position)) {
314 spectators.insert(player);
315 } else {
316 spectators.erase(player);
317 }
318
319 updateIdleStatus();
320 }
321 }
322}
323
324void Npc::onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text)
325{
326 if (creature->getID() == id) {
327 return;
328 }
329
330 //only players for script events
331 Player* player = creature->getPlayer();
332 if (player) {
333 if (npcEventHandler) {
334 npcEventHandler->onCreatureSay(player, type, text);
335 }
336 }
337}
338
339void Npc::onPlayerCloseChannel(Player* player)
340{
341 if (npcEventHandler) {
342 npcEventHandler->onPlayerCloseChannel(player);
343 }
344}
345
346void Npc::onThink(uint32_t interval)
347{
348 Creature::onThink(interval);
349
350 if (npcEventHandler) {
351 npcEventHandler->onThink();
352 }
353
354 if (!isIdle && getTimeSinceLastMove() >= walkTicks) {
355 addEventWalk();
356 }
357}
358
359void Npc::doSay(const std::string& text)
360{
361 g_game.internalCreatureSay(this, TALKTYPE_SAY, text, false);
362}
363
364void Npc::doSayToPlayer(Player* player, const std::string& text)
365{
366 if (player) {
367 player->sendCreatureSay(this, TALKTYPE_PRIVATE_NP, text);
368 player->onCreatureSay(this, TALKTYPE_PRIVATE_NP, text);
369 }
370}
371
372void Npc::onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count,
373 uint8_t amount, bool ignore/* = false*/, bool inBackpacks/* = false*/)
374{
375 if (npcEventHandler) {
376 npcEventHandler->onPlayerTrade(player, callback, itemId, count, amount, ignore, inBackpacks);
377 }
378 player->sendSaleItemList();
379}
380
381void Npc::onPlayerEndTrade(Player* player, int32_t buyCallback, int32_t sellCallback)
382{
383 lua_State* L = getScriptInterface()->getLuaState();
384
385 if (buyCallback != -1) {
386 luaL_unref(L, LUA_REGISTRYINDEX, buyCallback);
387 }
388
389 if (sellCallback != -1) {
390 luaL_unref(L, LUA_REGISTRYINDEX, sellCallback);
391 }
392
393 removeShopPlayer(player);
394
395 if (npcEventHandler) {
396 npcEventHandler->onPlayerEndTrade(player);
397 }
398}
399
400bool Npc::getNextStep(Direction& dir, uint32_t& flags)
401{
402 if (Creature::getNextStep(dir, flags)) {
403 return true;
404 }
405
406 if (walkTicks == 0) {
407 return false;
408 }
409
410 if (focusCreature != 0) {
411 return false;
412 }
413
414 if (getTimeSinceLastMove() < walkTicks) {
415 return false;
416 }
417
418 return getRandomStep(dir);
419}
420
421void Npc::setIdle(bool idle)
422{
423 if (isRemoved() || getHealth() <= 0) {
424 return;
425 }
426
427 isIdle = idle;
428
429 if (isIdle) {
430 onIdleStatus();
431 }
432}
433
434void Npc::updateIdleStatus()
435{
436 bool status = spectators.empty();
437 if (status != isIdle) {
438 setIdle(status);
439 }
440}
441
442bool Npc::canWalkTo(const Position& fromPos, Direction dir) const
443{
444 if (masterRadius == 0) {
445 return false;
446 }
447
448 Position toPos = getNextPosition(dir, fromPos);
449 if (!Spawns::isInZone(masterPos, masterRadius, toPos)) {
450 return false;
451 }
452
453 Tile* tile = g_game.map.getTile(toPos);
454 if (!tile || tile->queryAdd(0, *this, 1, 0) != RETURNVALUE_NOERROR) {
455 return false;
456 }
457
458 if (!floorChange && (tile->hasFlag(TILESTATE_FLOORCHANGE) || tile->getTeleportItem())) {
459 return false;
460 }
461
462 if (!ignoreHeight && tile->hasHeight(1)) {
463 return false;
464 }
465
466 return true;
467}
468
469bool Npc::getRandomStep(Direction& dir) const
470{
471 std::vector<Direction> dirList;
472 const Position& creaturePos = getPosition();
473
474 if (canWalkTo(creaturePos, DIRECTION_NORTH)) {
475 dirList.push_back(DIRECTION_NORTH);
476 }
477
478 if (canWalkTo(creaturePos, DIRECTION_SOUTH)) {
479 dirList.push_back(DIRECTION_SOUTH);
480 }
481
482 if (canWalkTo(creaturePos, DIRECTION_EAST)) {
483 dirList.push_back(DIRECTION_EAST);
484 }
485
486 if (canWalkTo(creaturePos, DIRECTION_WEST)) {
487 dirList.push_back(DIRECTION_WEST);
488 }
489
490 if (dirList.empty()) {
491 return false;
492 }
493
494 dir = dirList[uniform_random(0, dirList.size() - 1)];
495 return true;
496}
497
498void Npc::doMoveTo(const Position& pos)
499{
500 std::list<Direction> listDir;
501 if (getPathTo(pos, listDir, 1, 1, true, true)) {
502 startAutoWalk(listDir);
503 }
504}
505
506void Npc::turnToCreature(Creature* creature)
507{
508 const Position& creaturePos = creature->getPosition();
509 const Position& myPos = getPosition();
510 const auto dx = Position::getOffsetX(myPos, creaturePos);
511 const auto dy = Position::getOffsetY(myPos, creaturePos);
512
513 float tan;
514 if (dx != 0) {
515 tan = static_cast<float>(dy) / dx;
516 } else {
517 tan = 10;
518 }
519
520 Direction dir;
521 if (std::abs(tan) < 1) {
522 if (dx > 0) {
523 dir = DIRECTION_WEST;
524 } else {
525 dir = DIRECTION_EAST;
526 }
527 } else {
528 if (dy > 0) {
529 dir = DIRECTION_NORTH;
530 } else {
531 dir = DIRECTION_SOUTH;
532 }
533 }
534 g_game.internalCreatureTurn(this, dir);
535}
536
537void Npc::setCreatureFocus(Creature* creature)
538{
539 if (creature) {
540 focusCreature = creature->getID();
541 turnToCreature(creature);
542 } else {
543 focusCreature = 0;
544 }
545}
546
547void Npc::addShopPlayer(Player* player)
548{
549 shopPlayerSet.insert(player);
550}
551
552void Npc::removeShopPlayer(Player* player)
553{
554 shopPlayerSet.erase(player);
555}
556
557void Npc::closeAllShopWindows()
558{
559 while (!shopPlayerSet.empty()) {
560 Player* player = *shopPlayerSet.begin();
561 if (!player->closeShopWindow()) {
562 removeShopPlayer(player);
563 }
564 }
565}
566
567NpcScriptInterface* Npc::getScriptInterface()
568{
569 return scriptInterface;
570}
571
572NpcScriptInterface::NpcScriptInterface() :
573 LuaScriptInterface("Npc interface")
574{
575 libLoaded = false;
576 initState();
577}
578
579bool NpcScriptInterface::initState()
580{
581 luaState = g_luaEnvironment.getLuaState();
582 if (!luaState) {
583 return false;
584 }
585
586 registerFunctions();
587
588 lua_newtable(luaState);
589 eventTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX);
590 runningEventId = EVENT_ID_USER;
591 return true;
592}
593
594bool NpcScriptInterface::closeState()
595{
596 libLoaded = false;
597 LuaScriptInterface::closeState();
598 return true;
599}
600
601bool NpcScriptInterface::loadNpcLib(const std::string& file)
602{
603 if (libLoaded) {
604 return true;
605 }
606
607 if (loadFile(file) == -1) {
608 std::cout << "[Warning - NpcScriptInterface::loadNpcLib] Can not load " << file << std::endl;
609 return false;
610 }
611
612 libLoaded = true;
613 return true;
614}
615
616void NpcScriptInterface::registerFunctions()
617{
618 //npc exclusive functions
619 lua_register(luaState, "selfSay", NpcScriptInterface::luaActionSay);
620 lua_register(luaState, "selfMove", NpcScriptInterface::luaActionMove);
621 lua_register(luaState, "selfMoveTo", NpcScriptInterface::luaActionMoveTo);
622 lua_register(luaState, "selfTurn", NpcScriptInterface::luaActionTurn);
623 lua_register(luaState, "selfFollow", NpcScriptInterface::luaActionFollow);
624 lua_register(luaState, "getDistanceTo", NpcScriptInterface::luagetDistanceTo);
625 lua_register(luaState, "doNpcSetCreatureFocus", NpcScriptInterface::luaSetNpcFocus);
626 lua_register(luaState, "getNpcCid", NpcScriptInterface::luaGetNpcCid);
627 lua_register(luaState, "getNpcParameter", NpcScriptInterface::luaGetNpcParameter);
628 lua_register(luaState, "openShopWindow", NpcScriptInterface::luaOpenShopWindow);
629 lua_register(luaState, "closeShopWindow", NpcScriptInterface::luaCloseShopWindow);
630 lua_register(luaState, "doSellItem", NpcScriptInterface::luaDoSellItem);
631
632 // metatable
633 registerMethod("Npc", "getParameter", NpcScriptInterface::luaNpcGetParameter);
634 registerMethod("Npc", "setFocus", NpcScriptInterface::luaNpcSetFocus);
635
636 registerMethod("Npc", "openShopWindow", NpcScriptInterface::luaNpcOpenShopWindow);
637 registerMethod("Npc", "closeShopWindow", NpcScriptInterface::luaNpcCloseShopWindow);
638}
639
640int NpcScriptInterface::luaActionSay(lua_State* L)
641{
642 //selfSay(words[, target])
643 Npc* npc = getScriptEnv()->getNpc();
644 if (!npc) {
645 return 0;
646 }
647
648 const std::string& text = getString(L, 1);
649 if (lua_gettop(L) >= 2) {
650 Player* target = getPlayer(L, 2);
651 if (target) {
652 npc->doSayToPlayer(target, text);
653 return 0;
654 }
655 }
656
657 npc->doSay(text);
658 return 0;
659}
660
661int NpcScriptInterface::luaActionMove(lua_State* L)
662{
663 //selfMove(direction)
664 Npc* npc = getScriptEnv()->getNpc();
665 if (npc) {
666 g_game.internalMoveCreature(npc, getNumber<Direction>(L, 1));
667 }
668 return 0;
669}
670
671int NpcScriptInterface::luaActionMoveTo(lua_State* L)
672{
673 //selfMoveTo(x,y,z)
674 Npc* npc = getScriptEnv()->getNpc();
675 if (!npc) {
676 return 0;
677 }
678
679 npc->doMoveTo(Position(
680 getNumber<uint16_t>(L, 1),
681 getNumber<uint16_t>(L, 2),
682 getNumber<uint8_t>(L, 3)
683 ));
684 return 0;
685}
686
687int NpcScriptInterface::luaActionTurn(lua_State* L)
688{
689 //selfTurn(direction)
690 Npc* npc = getScriptEnv()->getNpc();
691 if (npc) {
692 g_game.internalCreatureTurn(npc, getNumber<Direction>(L, 1));
693 }
694 return 0;
695}
696
697int NpcScriptInterface::luaActionFollow(lua_State* L)
698{
699 //selfFollow(player)
700 Npc* npc = getScriptEnv()->getNpc();
701 if (!npc) {
702 pushBoolean(L, false);
703 return 1;
704 }
705
706 pushBoolean(L, npc->setFollowCreature(getPlayer(L, 1)));
707 return 1;
708}
709
710int NpcScriptInterface::luagetDistanceTo(lua_State* L)
711{
712 //getDistanceTo(uid)
713 ScriptEnvironment* env = getScriptEnv();
714
715 Npc* npc = env->getNpc();
716 if (!npc) {
717 reportErrorFunc(getErrorDesc(LUA_ERROR_THING_NOT_FOUND));
718 lua_pushnil(L);
719 return 1;
720 }
721
722 uint32_t uid = getNumber<uint32_t>(L, -1);
723
724 Thing* thing = env->getThingByUID(uid);
725 if (!thing) {
726 reportErrorFunc(getErrorDesc(LUA_ERROR_THING_NOT_FOUND));
727 lua_pushnil(L);
728 return 1;
729 }
730
731 const Position& thingPos = thing->getPosition();
732 const Position& npcPos = npc->getPosition();
733 if (npcPos.z != thingPos.z) {
734 lua_pushnumber(L, -1);
735 } else {
736 int32_t dist = std::max<int32_t>(Position::getDistanceX(npcPos, thingPos), Position::getDistanceY(npcPos, thingPos));
737 lua_pushnumber(L, dist);
738 }
739 return 1;
740}
741
742int NpcScriptInterface::luaSetNpcFocus(lua_State* L)
743{
744 //doNpcSetCreatureFocus(cid)
745 Npc* npc = getScriptEnv()->getNpc();
746 if (npc) {
747 npc->setCreatureFocus(getCreature(L, -1));
748 }
749 return 0;
750}
751
752int NpcScriptInterface::luaGetNpcCid(lua_State* L)
753{
754 //getNpcCid()
755 Npc* npc = getScriptEnv()->getNpc();
756 if (npc) {
757 lua_pushnumber(L, npc->getID());
758 } else {
759 lua_pushnil(L);
760 }
761 return 1;
762}
763
764int NpcScriptInterface::luaGetNpcParameter(lua_State* L)
765{
766 //getNpcParameter(paramKey)
767 Npc* npc = getScriptEnv()->getNpc();
768 if (!npc) {
769 lua_pushnil(L);
770 return 1;
771 }
772
773 std::string paramKey = getString(L, -1);
774
775 auto it = npc->parameters.find(paramKey);
776 if (it != npc->parameters.end()) {
777 LuaScriptInterface::pushString(L, it->second);
778 } else {
779 lua_pushnil(L);
780 }
781 return 1;
782}
783
784int NpcScriptInterface::luaOpenShopWindow(lua_State* L)
785{
786 //openShopWindow(cid, items, onBuy callback, onSell callback)
787 int32_t sellCallback;
788 if (lua_isfunction(L, -1) == 0) {
789 sellCallback = -1;
790 lua_pop(L, 1); // skip it - use default value
791 } else {
792 sellCallback = popCallback(L);
793 }
794
795 int32_t buyCallback;
796 if (lua_isfunction(L, -1) == 0) {
797 buyCallback = -1;
798 lua_pop(L, 1); // skip it - use default value
799 } else {
800 buyCallback = popCallback(L);
801 }
802
803 if (lua_istable(L, -1) == 0) {
804 reportError(__FUNCTION__, "item list is not a table.");
805 pushBoolean(L, false);
806 return 1;
807 }
808
809 std::list<ShopInfo> items;
810 lua_pushnil(L);
811 while (lua_next(L, -2) != 0) {
812 const auto tableIndex = lua_gettop(L);
813 ShopInfo item;
814
815 item.itemId = getField<uint32_t>(L, tableIndex, "id");
816 item.subType = getField<int32_t>(L, tableIndex, "subType");
817 if (item.subType == 0) {
818 item.subType = getField<int32_t>(L, tableIndex, "subtype");
819 lua_pop(L, 1);
820 }
821
822 item.buyPrice = getField<uint32_t>(L, tableIndex, "buy");
823 item.sellPrice = getField<uint32_t>(L, tableIndex, "sell");
824 item.realName = getFieldString(L, tableIndex, "name");
825
826 items.push_back(item);
827 lua_pop(L, 6);
828 }
829 lua_pop(L, 1);
830
831 Player* player = getPlayer(L, -1);
832 if (!player) {
833 reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND));
834 pushBoolean(L, false);
835 return 1;
836 }
837
838 //Close any eventual other shop window currently open.
839 player->closeShopWindow(false);
840
841 Npc* npc = getScriptEnv()->getNpc();
842 if (!npc) {
843 reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND));
844 pushBoolean(L, false);
845 return 1;
846 }
847
848 npc->addShopPlayer(player);
849 player->setShopOwner(npc, buyCallback, sellCallback);
850 player->openShopWindow(npc, items);
851
852 pushBoolean(L, true);
853 return 1;
854}
855
856int NpcScriptInterface::luaCloseShopWindow(lua_State* L)
857{
858 //closeShopWindow(cid)
859 Npc* npc = getScriptEnv()->getNpc();
860 if (!npc) {
861 reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND));
862 pushBoolean(L, false);
863 return 1;
864 }
865
866 Player* player = getPlayer(L, 1);
867 if (!player) {
868 reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND));
869 pushBoolean(L, false);
870 return 1;
871 }
872
873 int32_t buyCallback;
874 int32_t sellCallback;
875
876 Npc* merchant = player->getShopOwner(buyCallback, sellCallback);
877
878 //Check if we actually have a shop window with this player.
879 if (merchant == npc) {
880 player->sendCloseShop();
881
882 if (buyCallback != -1) {
883 luaL_unref(L, LUA_REGISTRYINDEX, buyCallback);
884 }
885
886 if (sellCallback != -1) {
887 luaL_unref(L, LUA_REGISTRYINDEX, sellCallback);
888 }
889
890 player->setShopOwner(nullptr, -1, -1);
891 npc->removeShopPlayer(player);
892 }
893
894 pushBoolean(L, true);
895 return 1;
896}
897
898int NpcScriptInterface::luaDoSellItem(lua_State* L)
899{
900 //doSellItem(cid, itemid, amount, <optional> subtype, <optional> actionid, <optional: default: 1> canDropOnMap)
901 Player* player = getPlayer(L, 1);
902 if (!player) {
903 reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND));
904 pushBoolean(L, false);
905 return 1;
906 }
907
908 uint32_t sellCount = 0;
909
910 uint32_t itemId = getNumber<uint32_t>(L, 2);
911 uint32_t amount = getNumber<uint32_t>(L, 3);
912 uint32_t subType;
913
914 int32_t n = getNumber<int32_t>(L, 4, -1);
915 if (n != -1) {
916 subType = n;
917 } else {
918 subType = 1;
919 }
920
921 uint32_t actionId = getNumber<uint32_t>(L, 5, 0);
922 bool canDropOnMap = getBoolean(L, 6, true);
923
924 const ItemType& it = Item::items[itemId];
925 if (it.stackable) {
926 while (amount > 0) {
927 int32_t stackCount = std::min<int32_t>(100, amount);
928 Item* item = Item::CreateItem(it.id, stackCount);
929 if (item && actionId != 0) {
930 item->setActionId(actionId);
931 }
932
933 if (g_game.internalPlayerAddItem(player, item, canDropOnMap) != RETURNVALUE_NOERROR) {
934 delete item;
935 lua_pushnumber(L, sellCount);
936 return 1;
937 }
938
939 amount -= stackCount;
940 sellCount += stackCount;
941 }
942 } else {
943 for (uint32_t i = 0; i < amount; ++i) {
944 Item* item = Item::CreateItem(it.id, subType);
945 if (item && actionId != 0) {
946 item->setActionId(actionId);
947 }
948
949 if (g_game.internalPlayerAddItem(player, item, canDropOnMap) != RETURNVALUE_NOERROR) {
950 delete item;
951 lua_pushnumber(L, sellCount);
952 return 1;
953 }
954
955 ++sellCount;
956 }
957 }
958
959 lua_pushnumber(L, sellCount);
960 return 1;
961}
962
963int NpcScriptInterface::luaNpcGetParameter(lua_State* L)
964{
965 // npc:getParameter(key)
966 const std::string& key = getString(L, 2);
967 Npc* npc = getUserdata<Npc>(L, 1);
968 if (npc) {
969 auto it = npc->parameters.find(key);
970 if (it != npc->parameters.end()) {
971 pushString(L, it->second);
972 } else {
973 lua_pushnil(L);
974 }
975 } else {
976 lua_pushnil(L);
977 }
978 return 1;
979}
980
981int NpcScriptInterface::luaNpcSetFocus(lua_State* L)
982{
983 // npc:setFocus(creature)
984 Creature* creature = getCreature(L, 2);
985 Npc* npc = getUserdata<Npc>(L, 1);
986 if (npc) {
987 npc->setCreatureFocus(creature);
988 pushBoolean(L, true);
989 } else {
990 lua_pushnil(L);
991 }
992 return 1;
993}
994
995int NpcScriptInterface::luaNpcOpenShopWindow(lua_State* L)
996{
997 // npc:openShopWindow(cid, items, buyCallback, sellCallback)
998 if (!isTable(L, 3)) {
999 reportErrorFunc("item list is not a table.");
1000 pushBoolean(L, false);
1001 return 1;
1002 }
1003
1004 Player* player = getPlayer(L, 2);
1005 if (!player) {
1006 reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND));
1007 pushBoolean(L, false);
1008 return 1;
1009 }
1010
1011 Npc* npc = getUserdata<Npc>(L, 1);
1012 if (!npc) {
1013 reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND));
1014 pushBoolean(L, false);
1015 return 1;
1016 }
1017
1018 int32_t sellCallback = -1;
1019 if (LuaScriptInterface::isFunction(L, 5)) {
1020 sellCallback = luaL_ref(L, LUA_REGISTRYINDEX);
1021 }
1022
1023 int32_t buyCallback = -1;
1024 if (LuaScriptInterface::isFunction(L, 4)) {
1025 buyCallback = luaL_ref(L, LUA_REGISTRYINDEX);
1026 }
1027
1028 std::list<ShopInfo> items;
1029
1030 lua_pushnil(L);
1031 while (lua_next(L, 3) != 0) {
1032 const auto tableIndex = lua_gettop(L);
1033 ShopInfo item;
1034
1035 item.itemId = getField<uint32_t>(L, tableIndex, "id");
1036 item.subType = getField<int32_t>(L, tableIndex, "subType");
1037 if (item.subType == 0) {
1038 item.subType = getField<int32_t>(L, tableIndex, "subtype");
1039 lua_pop(L, 1);
1040 }
1041
1042 item.buyPrice = getField<uint32_t>(L, tableIndex, "buy");
1043 item.sellPrice = getField<uint32_t>(L, tableIndex, "sell");
1044 item.realName = getFieldString(L, tableIndex, "name");
1045
1046 items.push_back(item);
1047 lua_pop(L, 6);
1048 }
1049 lua_pop(L, 1);
1050
1051 player->closeShopWindow(false);
1052 npc->addShopPlayer(player);
1053
1054 player->setShopOwner(npc, buyCallback, sellCallback);
1055 player->openShopWindow(npc, items);
1056
1057 pushBoolean(L, true);
1058 return 1;
1059}
1060
1061int NpcScriptInterface::luaNpcCloseShopWindow(lua_State* L)
1062{
1063 // npc:closeShopWindow(player)
1064 Player* player = getPlayer(L, 2);
1065 if (!player) {
1066 reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND));
1067 pushBoolean(L, false);
1068 return 1;
1069 }
1070
1071 Npc* npc = getUserdata<Npc>(L, 1);
1072 if (!npc) {
1073 reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND));
1074 pushBoolean(L, false);
1075 return 1;
1076 }
1077
1078 int32_t buyCallback;
1079 int32_t sellCallback;
1080
1081 Npc* merchant = player->getShopOwner(buyCallback, sellCallback);
1082 if (merchant == npc) {
1083 player->sendCloseShop();
1084 if (buyCallback != -1) {
1085 luaL_unref(L, LUA_REGISTRYINDEX, buyCallback);
1086 }
1087
1088 if (sellCallback != -1) {
1089 luaL_unref(L, LUA_REGISTRYINDEX, sellCallback);
1090 }
1091
1092 player->setShopOwner(nullptr, -1, -1);
1093 npc->removeShopPlayer(player);
1094 }
1095
1096 pushBoolean(L, true);
1097 return 1;
1098}
1099
1100NpcEventsHandler::NpcEventsHandler(const std::string& file, Npc* npc) :
1101 npc(npc), scriptInterface(npc->getScriptInterface())
1102{
1103 loaded = scriptInterface->loadFile("data/npc/scripts/" + file, npc) == 0;
1104 if (!loaded) {
1105 std::cout << "[Warning - NpcScript::NpcScript] Can not load script: " << file << std::endl;
1106 std::cout << scriptInterface->getLastLuaError() << std::endl;
1107 } else {
1108 creatureSayEvent = scriptInterface->getEvent("onCreatureSay");
1109 creatureDisappearEvent = scriptInterface->getEvent("onCreatureDisappear");
1110 creatureAppearEvent = scriptInterface->getEvent("onCreatureAppear");
1111 creatureMoveEvent = scriptInterface->getEvent("onCreatureMove");
1112 playerCloseChannelEvent = scriptInterface->getEvent("onPlayerCloseChannel");
1113 playerEndTradeEvent = scriptInterface->getEvent("onPlayerEndTrade");
1114 thinkEvent = scriptInterface->getEvent("onThink");
1115 }
1116}
1117
1118bool NpcEventsHandler::isLoaded() const
1119{
1120 return loaded;
1121}
1122
1123void NpcEventsHandler::onCreatureAppear(Creature* creature)
1124{
1125 if (creatureAppearEvent == -1) {
1126 return;
1127 }
1128
1129 //onCreatureAppear(creature)
1130 if (!scriptInterface->reserveScriptEnv()) {
1131 std::cout << "[Error - NpcScript::onCreatureAppear] Call stack overflow" << std::endl;
1132 return;
1133 }
1134
1135 ScriptEnvironment* env = scriptInterface->getScriptEnv();
1136 env->setScriptId(creatureAppearEvent, scriptInterface);
1137 env->setNpc(npc);
1138
1139 lua_State* L = scriptInterface->getLuaState();
1140 scriptInterface->pushFunction(creatureAppearEvent);
1141 LuaScriptInterface::pushUserdata<Creature>(L, creature);
1142 LuaScriptInterface::setCreatureMetatable(L, -1, creature);
1143 scriptInterface->callFunction(1);
1144}
1145
1146void NpcEventsHandler::onCreatureDisappear(Creature* creature)
1147{
1148 if (creatureDisappearEvent == -1) {
1149 return;
1150 }
1151
1152 //onCreatureDisappear(creature)
1153 if (!scriptInterface->reserveScriptEnv()) {
1154 std::cout << "[Error - NpcScript::onCreatureDisappear] Call stack overflow" << std::endl;
1155 return;
1156 }
1157
1158 ScriptEnvironment* env = scriptInterface->getScriptEnv();
1159 env->setScriptId(creatureDisappearEvent, scriptInterface);
1160 env->setNpc(npc);
1161
1162 lua_State* L = scriptInterface->getLuaState();
1163 scriptInterface->pushFunction(creatureDisappearEvent);
1164 LuaScriptInterface::pushUserdata<Creature>(L, creature);
1165 LuaScriptInterface::setCreatureMetatable(L, -1, creature);
1166 scriptInterface->callFunction(1);
1167}
1168
1169void NpcEventsHandler::onCreatureMove(Creature* creature, const Position& oldPos, const Position& newPos)
1170{
1171 if (creatureMoveEvent == -1) {
1172 return;
1173 }
1174
1175 //onCreatureMove(creature, oldPos, newPos)
1176 if (!scriptInterface->reserveScriptEnv()) {
1177 std::cout << "[Error - NpcScript::onCreatureMove] Call stack overflow" << std::endl;
1178 return;
1179 }
1180
1181 ScriptEnvironment* env = scriptInterface->getScriptEnv();
1182 env->setScriptId(creatureMoveEvent, scriptInterface);
1183 env->setNpc(npc);
1184
1185 lua_State* L = scriptInterface->getLuaState();
1186 scriptInterface->pushFunction(creatureMoveEvent);
1187 LuaScriptInterface::pushUserdata<Creature>(L, creature);
1188 LuaScriptInterface::setCreatureMetatable(L, -1, creature);
1189 LuaScriptInterface::pushPosition(L, oldPos);
1190 LuaScriptInterface::pushPosition(L, newPos);
1191 scriptInterface->callFunction(3);
1192}
1193
1194void NpcEventsHandler::onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text)
1195{
1196 if (creatureSayEvent == -1) {
1197 return;
1198 }
1199
1200 //onCreatureSay(creature, type, msg)
1201 if (!scriptInterface->reserveScriptEnv()) {
1202 std::cout << "[Error - NpcScript::onCreatureSay] Call stack overflow" << std::endl;
1203 return;
1204 }
1205
1206 ScriptEnvironment* env = scriptInterface->getScriptEnv();
1207 env->setScriptId(creatureSayEvent, scriptInterface);
1208 env->setNpc(npc);
1209
1210 lua_State* L = scriptInterface->getLuaState();
1211 scriptInterface->pushFunction(creatureSayEvent);
1212 LuaScriptInterface::pushUserdata<Creature>(L, creature);
1213 LuaScriptInterface::setCreatureMetatable(L, -1, creature);
1214 lua_pushnumber(L, type);
1215 LuaScriptInterface::pushString(L, text);
1216 scriptInterface->callFunction(3);
1217}
1218
1219void NpcEventsHandler::onPlayerTrade(Player* player, int32_t callback, uint16_t itemId,
1220 uint8_t count, uint8_t amount, bool ignore, bool inBackpacks)
1221{
1222 if (callback == -1) {
1223 return;
1224 }
1225
1226 //onBuy(player, itemid, count, amount, ignore, inbackpacks)
1227 if (!scriptInterface->reserveScriptEnv()) {
1228 std::cout << "[Error - NpcScript::onPlayerTrade] Call stack overflow" << std::endl;
1229 return;
1230 }
1231
1232 ScriptEnvironment* env = scriptInterface->getScriptEnv();
1233 env->setScriptId(-1, scriptInterface);
1234 env->setNpc(npc);
1235
1236 lua_State* L = scriptInterface->getLuaState();
1237 LuaScriptInterface::pushCallback(L, callback);
1238 LuaScriptInterface::pushUserdata<Player>(L, player);
1239 LuaScriptInterface::setMetatable(L, -1, "Player");
1240 lua_pushnumber(L, itemId);
1241 lua_pushnumber(L, count);
1242 lua_pushnumber(L, amount);
1243 LuaScriptInterface::pushBoolean(L, ignore);
1244 LuaScriptInterface::pushBoolean(L, inBackpacks);
1245 scriptInterface->callFunction(6);
1246}
1247
1248void NpcEventsHandler::onPlayerCloseChannel(Player* player)
1249{
1250 if (playerCloseChannelEvent == -1) {
1251 return;
1252 }
1253
1254 //onPlayerCloseChannel(player)
1255 if (!scriptInterface->reserveScriptEnv()) {
1256 std::cout << "[Error - NpcScript::onPlayerCloseChannel] Call stack overflow" << std::endl;
1257 return;
1258 }
1259
1260 ScriptEnvironment* env = scriptInterface->getScriptEnv();
1261 env->setScriptId(playerCloseChannelEvent, scriptInterface);
1262 env->setNpc(npc);
1263
1264 lua_State* L = scriptInterface->getLuaState();
1265 scriptInterface->pushFunction(playerCloseChannelEvent);
1266 LuaScriptInterface::pushUserdata<Player>(L, player);
1267 LuaScriptInterface::setMetatable(L, -1, "Player");
1268 scriptInterface->callFunction(1);
1269}
1270
1271void NpcEventsHandler::onPlayerEndTrade(Player* player)
1272{
1273 if (playerEndTradeEvent == -1) {
1274 return;
1275 }
1276
1277 //onPlayerEndTrade(player)
1278 if (!scriptInterface->reserveScriptEnv()) {
1279 std::cout << "[Error - NpcScript::onPlayerEndTrade] Call stack overflow" << std::endl;
1280 return;
1281 }
1282
1283 ScriptEnvironment* env = scriptInterface->getScriptEnv();
1284 env->setScriptId(playerEndTradeEvent, scriptInterface);
1285 env->setNpc(npc);
1286
1287 lua_State* L = scriptInterface->getLuaState();
1288 scriptInterface->pushFunction(playerEndTradeEvent);
1289 LuaScriptInterface::pushUserdata<Player>(L, player);
1290 LuaScriptInterface::setMetatable(L, -1, "Player");
1291 scriptInterface->callFunction(1);
1292}
1293
1294void NpcEventsHandler::onThink()
1295{
1296 if (thinkEvent == -1) {
1297 return;
1298 }
1299
1300 //onThink()
1301 if (!scriptInterface->reserveScriptEnv()) {
1302 std::cout << "[Error - NpcScript::onThink] Call stack overflow" << std::endl;
1303 return;
1304 }
1305
1306 ScriptEnvironment* env = scriptInterface->getScriptEnv();
1307 env->setScriptId(thinkEvent, scriptInterface);
1308 env->setNpc(npc);
1309
1310 scriptInterface->pushFunction(thinkEvent);
1311 scriptInterface->callFunction(0);
1312}
1313
Top