actions.cpp

main 584 lines · 17.5 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 "actions.h"
23#include "bed.h"
24#include "configmanager.h"
25#include "container.h"
26#include "game.h"
27#include "pugicast.h"
28#include "spells.h"
29
30extern Game g_game;
31extern Spells* g_spells;
32extern Actions* g_actions;
33extern ConfigManager g_config;
34
35Actions::Actions() :
36 scriptInterface("Action Interface")
37{
38 scriptInterface.initState();
39}
40
41Actions::~Actions()
42{
43 clear(false);
44}
45
46void Actions::clearMap(ActionUseMap& map, bool fromLua)
47{
48 for (auto it = map.begin(); it != map.end(); ) {
49 if (fromLua == it->second.fromLua) {
50 it = map.erase(it);
51 } else {
52 ++it;
53 }
54 }
55}
56
57void Actions::clear(bool fromLua)
58{
59 clearMap(useItemMap, fromLua);
60 clearMap(uniqueItemMap, fromLua);
61 clearMap(actionItemMap, fromLua);
62
63 reInitState(fromLua);
64}
65
66LuaScriptInterface& Actions::getScriptInterface()
67{
68 return scriptInterface;
69}
70
71std::string Actions::getScriptBaseName() const
72{
73 return "actions";
74}
75
76Event_ptr Actions::getEvent(const std::string& nodeName)
77{
78 if (strcasecmp(nodeName.c_str(), "action") != 0) {
79 return nullptr;
80 }
81 return Event_ptr(new Action(&scriptInterface));
82}
83
84bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node)
85{
86 Action_ptr action{static_cast<Action*>(event.release())}; //event is guaranteed to be an Action
87
88 pugi::xml_attribute attr;
89 if ((attr = node.attribute("itemid"))) {
90 uint16_t id = pugi::cast<uint16_t>(attr.value());
91
92 auto result = useItemMap.emplace(id, std::move(*action));
93 if (!result.second) {
94 std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << id << std::endl;
95 }
96 return result.second;
97 } else if ((attr = node.attribute("fromid"))) {
98 pugi::xml_attribute toIdAttribute = node.attribute("toid");
99 if (!toIdAttribute) {
100 std::cout << "[Warning - Actions::registerEvent] Missing toid in fromid: " << attr.as_string() << std::endl;
101 return false;
102 }
103
104 uint16_t fromId = pugi::cast<uint16_t>(attr.value());
105 uint16_t iterId = fromId;
106 uint16_t toId = pugi::cast<uint16_t>(toIdAttribute.value());
107
108 auto result = useItemMap.emplace(iterId, *action);
109 if (!result.second) {
110 std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << iterId << " in fromid: " << fromId << ", toid: " << toId << std::endl;
111 }
112
113 bool success = result.second;
114 while (++iterId <= toId) {
115 result = useItemMap.emplace(iterId, *action);
116 if (!result.second) {
117 std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << iterId << " in fromid: " << fromId << ", toid: " << toId << std::endl;
118 continue;
119 }
120 success = true;
121 }
122 return success;
123 } else if ((attr = node.attribute("uniqueid"))) {
124 uint16_t uid = pugi::cast<uint16_t>(attr.value());
125
126 auto result = uniqueItemMap.emplace(uid, std::move(*action));
127 if (!result.second) {
128 std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with uniqueid: " << uid << std::endl;
129 }
130 return result.second;
131 } else if ((attr = node.attribute("fromuid"))) {
132 pugi::xml_attribute toUidAttribute = node.attribute("touid");
133 if (!toUidAttribute) {
134 std::cout << "[Warning - Actions::registerEvent] Missing touid in fromuid: " << attr.as_string() << std::endl;
135 return false;
136 }
137
138 uint16_t fromUid = pugi::cast<uint16_t>(attr.value());
139 uint16_t iterUid = fromUid;
140 uint16_t toUid = pugi::cast<uint16_t>(toUidAttribute.value());
141
142 auto result = uniqueItemMap.emplace(iterUid, *action);
143 if (!result.second) {
144 std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with unique id: " << iterUid << " in fromuid: " << fromUid << ", touid: " << toUid << std::endl;
145 }
146
147 bool success = result.second;
148 while (++iterUid <= toUid) {
149 result = uniqueItemMap.emplace(iterUid, *action);
150 if (!result.second) {
151 std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with unique id: " << iterUid << " in fromuid: " << fromUid << ", touid: " << toUid << std::endl;
152 continue;
153 }
154 success = true;
155 }
156 return success;
157 } else if ((attr = node.attribute("actionid"))) {
158 uint16_t aid = pugi::cast<uint16_t>(attr.value());
159
160 auto result = actionItemMap.emplace(aid, std::move(*action));
161 if (!result.second) {
162 std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with actionid: " << aid << std::endl;
163 }
164 return result.second;
165 } else if ((attr = node.attribute("fromaid"))) {
166 pugi::xml_attribute toAidAttribute = node.attribute("toaid");
167 if (!toAidAttribute) {
168 std::cout << "[Warning - Actions::registerEvent] Missing toaid in fromaid: " << attr.as_string() << std::endl;
169 return false;
170 }
171
172 uint16_t fromAid = pugi::cast<uint16_t>(attr.value());
173 uint16_t iterAid = fromAid;
174 uint16_t toAid = pugi::cast<uint16_t>(toAidAttribute.value());
175
176 auto result = actionItemMap.emplace(iterAid, *action);
177 if (!result.second) {
178 std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with action id: " << iterAid << " in fromaid: " << fromAid << ", toaid: " << toAid << std::endl;
179 }
180
181 bool success = result.second;
182 while (++iterAid <= toAid) {
183 result = actionItemMap.emplace(iterAid, *action);
184 if (!result.second) {
185 std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with action id: " << iterAid << " in fromaid: " << fromAid << ", toaid: " << toAid << std::endl;
186 continue;
187 }
188 success = true;
189 }
190 return success;
191 }
192 return false;
193}
194
195bool Actions::registerLuaEvent(Action* event)
196{
197 Action_ptr action{ event };
198 if (action->getItemIdRange().size() > 0) {
199 if (action->getItemIdRange().size() == 1) {
200 auto id = action->getItemIdRange().front();
201 auto result = useItemMap.emplace(id, std::move(*action));
202 if (!result.second) {
203 std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with id: " << id << std::endl;
204 }
205 return result.second;
206 } else {
207 auto v = action->getItemIdRange();
208 for (auto i = v.begin(); i != v.end(); i++) {
209 auto result = useItemMap.emplace(*i, std::move(*action));
210 if (!result.second) {
211 std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with id: " << *i << " in range from id: " << v.front() << ", to id: " << v.at(v.size() - 1) << std::endl;
212 continue;
213 }
214 }
215 return true;
216 }
217 } else if (action->getUniqueIdRange().size() > 0) {
218 if (action->getUniqueIdRange().size() == 1) {
219 auto uid = action->getUniqueIdRange().front();
220 auto result = uniqueItemMap.emplace(uid, std::move(*action));
221 if (!result.second) {
222 std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with uid: " << uid << std::endl;
223 }
224 return result.second;
225 } else {
226 auto v = action->getUniqueIdRange();
227 for (auto i = v.begin(); i != v.end(); i++) {
228 auto result = uniqueItemMap.emplace(*i, std::move(*action));
229 if (!result.second) {
230 std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with uid: " << *i << " in range from uid: " << v.front() << ", to uid: " << v.at(v.size() - 1) << std::endl;
231 continue;
232 }
233 }
234 return true;
235 }
236 } else if (action->getActionIdRange().size() > 0) {
237 if (action->getActionIdRange().size() == 1) {
238 auto aid = action->getActionIdRange().front();
239 auto result = actionItemMap.emplace(aid, std::move(*action));
240 if (!result.second) {
241 std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with aid: " << aid << std::endl;
242 }
243 return result.second;
244 } else {
245 auto v = action->getActionIdRange();
246 for (auto i = v.begin(); i != v.end(); i++) {
247 auto result = actionItemMap.emplace(*i, std::move(*action));
248 if (!result.second) {
249 std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with aid: " << *i << " in range from aid: " << v.front() << ", to aid: " << v.at(v.size() - 1) << std::endl;
250 continue;
251 }
252 }
253 return true;
254 }
255 } else {
256 std::cout << "[Warning - Actions::registerLuaEvent] There is no id / aid / uid set for this event" << std::endl;
257 return false;
258 }
259}
260
261ReturnValue Actions::canUse(const Player* player, const Position& pos)
262{
263 if (pos.x != 0xFFFF) {
264 const Position& playerPos = player->getPosition();
265 if (playerPos.z != pos.z) {
266 return playerPos.z > pos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS;
267 }
268
269 if (!Position::areInRange<1, 1>(playerPos, pos)) {
270 return RETURNVALUE_TOOFARAWAY;
271 }
272 }
273 return RETURNVALUE_NOERROR;
274}
275
276ReturnValue Actions::canUse(const Player* player, const Position& pos, const Item* item)
277{
278 Action* action = getAction(item);
279 if (action) {
280 return action->canExecuteAction(player, pos);
281 }
282 return RETURNVALUE_NOERROR;
283}
284
285ReturnValue Actions::canUseFar(const Creature* creature, const Position& toPos, bool checkLineOfSight, bool checkFloor)
286{
287 if (toPos.x == 0xFFFF) {
288 return RETURNVALUE_NOERROR;
289 }
290
291 const Position& creaturePos = creature->getPosition();
292 if (checkFloor && creaturePos.z != toPos.z) {
293 return creaturePos.z > toPos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS;
294 }
295
296 if (!Position::areInRange<7, 5>(toPos, creaturePos)) {
297 return RETURNVALUE_TOOFARAWAY;
298 }
299
300 if (checkLineOfSight && !g_game.canThrowObjectTo(creaturePos, toPos)) {
301 return RETURNVALUE_CANNOTTHROW;
302 }
303
304 return RETURNVALUE_NOERROR;
305}
306
307Action* Actions::getAction(const Item* item)
308{
309 if (item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) {
310 auto it = uniqueItemMap.find(item->getUniqueId());
311 if (it != uniqueItemMap.end()) {
312 return &it->second;
313 }
314 }
315
316 if (item->hasAttribute(ITEM_ATTRIBUTE_ACTIONID)) {
317 auto it = actionItemMap.find(item->getActionId());
318 if (it != actionItemMap.end()) {
319 return &it->second;
320 }
321 }
322
323 auto it = useItemMap.find(item->getID());
324 if (it != useItemMap.end()) {
325 return &it->second;
326 }
327
328 //rune items
329 return g_spells->getRuneSpell(item->getID());
330}
331
332ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey)
333{
334 if (Door* door = item->getDoor()) {
335 if (!door->canUse(player)) {
336 return RETURNVALUE_CANNOTUSETHISOBJECT;
337 }
338 }
339
340 Action* action = getAction(item);
341 if (action) {
342 if (action->isScripted()) {
343 if (action->executeUse(player, item, pos, nullptr, pos, isHotkey)) {
344 return RETURNVALUE_NOERROR;
345 }
346
347 if (item->isRemoved()) {
348 return RETURNVALUE_CANNOTUSETHISOBJECT;
349 }
350 } else if (action->function) {
351 if (action->function(player, item, pos, nullptr, pos, isHotkey)) {
352 return RETURNVALUE_NOERROR;
353 }
354 }
355 }
356
357 if (BedItem* bed = item->getBed()) {
358 if (!bed->canUse(player)) {
359 return RETURNVALUE_CANNOTUSETHISOBJECT;
360 }
361
362 if (bed->trySleep(player)) {
363 player->setBedItem(bed);
364 g_game.sendOfflineTrainingDialog(player);
365 }
366
367 return RETURNVALUE_NOERROR;
368 }
369
370 if (Container* container = item->getContainer()) {
371 Container* openContainer;
372
373 //depot container
374 if (DepotLocker* depot = container->getDepotLocker()) {
375 DepotLocker* myDepotLocker = player->getDepotLocker(depot->getDepotId());
376 myDepotLocker->setParent(depot->getParent()->getTile());
377 openContainer = myDepotLocker;
378 player->setLastDepotId(depot->getDepotId());
379 } else {
380 openContainer = container;
381 }
382
383 uint32_t corpseOwner = container->getCorpseOwner();
384 if (corpseOwner != 0 && !player->canOpenCorpse(corpseOwner)) {
385 return RETURNVALUE_YOUARENOTTHEOWNER;
386 }
387
388 //open/close container
389 int32_t oldContainerId = player->getContainerID(openContainer);
390 if (oldContainerId != -1) {
391 player->onCloseContainer(openContainer);
392 player->closeContainer(oldContainerId);
393 } else {
394 player->addContainer(index, openContainer);
395 player->onSendContainer(openContainer);
396 }
397
398 return RETURNVALUE_NOERROR;
399 }
400
401 const ItemType& it = Item::items[item->getID()];
402 if (it.canReadText) {
403 if (it.canWriteText) {
404 player->setWriteItem(item, it.maxTextLen);
405 player->sendTextWindow(item, it.maxTextLen, true);
406 } else {
407 player->setWriteItem(nullptr);
408 player->sendTextWindow(item, 0, false);
409 }
410
411 return RETURNVALUE_NOERROR;
412 }
413
414 return RETURNVALUE_CANNOTUSETHISOBJECT;
415}
416
417bool Actions::useItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey)
418{
419 player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::ACTIONS_DELAY_INTERVAL));
420
421 if (isHotkey) {
422 uint16_t subType = item->getSubType();
423 showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), subType != item->getItemCount() ? subType : -1));
424 }
425
426 ReturnValue ret = internalUseItem(player, pos, index, item, isHotkey);
427 if (ret != RETURNVALUE_NOERROR) {
428 player->sendCancelMessage(ret);
429 return false;
430 }
431 return true;
432}
433
434bool Actions::useItemEx(Player* player, const Position& fromPos, const Position& toPos,
435 uint8_t toStackPos, Item* item, bool isHotkey, Creature* creature/* = nullptr*/)
436{
437 player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::EX_ACTIONS_DELAY_INTERVAL));
438
439 Action* action = getAction(item);
440 if (!action) {
441 player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
442 return false;
443 }
444
445 ReturnValue ret = action->canExecuteAction(player, toPos);
446 if (ret != RETURNVALUE_NOERROR) {
447 player->sendCancelMessage(ret);
448 return false;
449 }
450
451 if (isHotkey) {
452 uint16_t subType = item->getSubType();
453 showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), subType != item->getItemCount() ? subType : -1));
454 }
455
456 if (!action->executeUse(player, item, fromPos, action->getTarget(player, creature, toPos, toStackPos), toPos, isHotkey)) {
457 if (!action->hasOwnErrorHandler()) {
458 player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
459 }
460 return false;
461 }
462 return true;
463}
464
465void Actions::showUseHotkeyMessage(Player* player, const Item* item, uint32_t count)
466{
467 std::ostringstream ss;
468
469 const ItemType& it = Item::items[item->getID()];
470 if (!it.showCount) {
471 ss << "Using one of " << item->getName() << "...";
472 } else if (count == 1) {
473 ss << "Using the last " << item->getName() << "...";
474 } else {
475 ss << "Using one of " << count << ' ' << item->getPluralName() << "...";
476 }
477 player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str());
478}
479
480Action::Action(LuaScriptInterface* interface) :
481 Event(interface), function(nullptr), allowFarUse(false), checkFloor(true), checkLineOfSight(true) {}
482
483bool Action::configureEvent(const pugi::xml_node& node)
484{
485 pugi::xml_attribute allowFarUseAttr = node.attribute("allowfaruse");
486 if (allowFarUseAttr) {
487 allowFarUse = allowFarUseAttr.as_bool();
488 }
489
490 pugi::xml_attribute blockWallsAttr = node.attribute("blockwalls");
491 if (blockWallsAttr) {
492 checkLineOfSight = blockWallsAttr.as_bool();
493 }
494
495 pugi::xml_attribute checkFloorAttr = node.attribute("checkfloor");
496 if (checkFloorAttr) {
497 checkFloor = checkFloorAttr.as_bool();
498 }
499
500 return true;
501}
502
503namespace {
504
505bool enterMarket(Player* player, Item*, const Position&, Thing*, const Position&, bool)
506{
507 if (player->getLastDepotId() == -1) {
508 return false;
509 }
510
511 player->sendMarketEnter(player->getLastDepotId());
512 return true;
513}
514
515}
516
517bool Action::loadFunction(const pugi::xml_attribute& attr, bool isScripted)
518{
519 const char* functionName = attr.as_string();
520 if (strcasecmp(functionName, "market") == 0) {
521 function = enterMarket;
522 } else {
523 if (!isScripted) {
524 std::cout << "[Warning - Action::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl;
525 return false;
526 }
527 }
528
529 if (!isScripted) {
530 scripted = false;
531 }
532 return true;
533}
534
535std::string Action::getScriptEventName() const
536{
537 return "onUse";
538}
539
540ReturnValue Action::canExecuteAction(const Player* player, const Position& toPos)
541{
542 if (!allowFarUse) {
543 return g_actions->canUse(player, toPos);
544 } else {
545 return g_actions->canUseFar(player, toPos, checkLineOfSight, checkFloor);
546 }
547}
548
549Thing* Action::getTarget(Player* player, Creature* targetCreature, const Position& toPosition, uint8_t toStackPos) const
550{
551 if (targetCreature) {
552 return targetCreature;
553 }
554 return g_game.internalGetThing(player, toPosition, toStackPos, 0, STACKPOS_USETARGET);
555}
556
557bool Action::executeUse(Player* player, Item* item, const Position& fromPosition, Thing* target, const Position& toPosition, bool isHotkey)
558{
559 //onUse(player, item, fromPosition, target, toPosition, isHotkey)
560 if (!scriptInterface->reserveScriptEnv()) {
561 std::cout << "[Error - Action::executeUse] Call stack overflow" << std::endl;
562 return false;
563 }
564
565 ScriptEnvironment* env = scriptInterface->getScriptEnv();
566 env->setScriptId(scriptId, scriptInterface);
567
568 lua_State* L = scriptInterface->getLuaState();
569
570 scriptInterface->pushFunction(scriptId);
571
572 LuaScriptInterface::pushUserdata<Player>(L, player);
573 LuaScriptInterface::setMetatable(L, -1, "Player");
574
575 LuaScriptInterface::pushThing(L, item);
576 LuaScriptInterface::pushPosition(L, fromPosition);
577
578 LuaScriptInterface::pushThing(L, target);
579 LuaScriptInterface::pushPosition(L, toPosition);
580
581 LuaScriptInterface::pushBoolean(L, isHotkey);
582 return scriptInterface->callFunction(6);
583}
584
Top