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 "game.h"
23
24#include "pugicast.h"
25
26#include "movement.h"
27
28extern Game g_game;
29extern Vocations g_vocations;
30
31MoveEvents::MoveEvents() :
32 scriptInterface("MoveEvents Interface")
33{
34 scriptInterface.initState();
35}
36
37MoveEvents::~MoveEvents()
38{
39 clear(false);
40}
41
42void MoveEvents::clearMap(MoveListMap& map, bool fromLua)
43{
44 for (auto it = map.begin(); it != map.end(); ++it) {
45 for (int eventType = MOVE_EVENT_STEP_IN; eventType < MOVE_EVENT_LAST; ++eventType) {
46 auto& moveEvents = it->second.moveEvent[eventType];
47 for (auto find = moveEvents.begin(); find != moveEvents.end(); ) {
48 if (fromLua == find->fromLua) {
49 find = moveEvents.erase(find);
50 } else {
51 ++find;
52 }
53 }
54 }
55 }
56}
57
58void MoveEvents::clearPosMap(MovePosListMap& map, bool fromLua)
59{
60 for (auto it = map.begin(); it != map.end(); ++it) {
61 for (int eventType = MOVE_EVENT_STEP_IN; eventType < MOVE_EVENT_LAST; ++eventType) {
62 auto& moveEvents = it->second.moveEvent[eventType];
63 for (auto find = moveEvents.begin(); find != moveEvents.end(); ) {
64 if (fromLua == find->fromLua) {
65 find = moveEvents.erase(find);
66 } else {
67 ++find;
68 }
69 }
70 }
71 }
72}
73
74void MoveEvents::clear(bool fromLua)
75{
76 clearMap(itemIdMap, fromLua);
77 clearMap(actionIdMap, fromLua);
78 clearMap(uniqueIdMap, fromLua);
79 clearPosMap(positionMap, fromLua);
80
81 reInitState(fromLua);
82}
83
84LuaScriptInterface& MoveEvents::getScriptInterface()
85{
86 return scriptInterface;
87}
88
89std::string MoveEvents::getScriptBaseName() const
90{
91 return "movements";
92}
93
94Event_ptr MoveEvents::getEvent(const std::string& nodeName)
95{
96 if (strcasecmp(nodeName.c_str(), "movevent") != 0) {
97 return nullptr;
98 }
99 return Event_ptr(new MoveEvent(&scriptInterface));
100}
101
102bool MoveEvents::registerEvent(Event_ptr event, const pugi::xml_node& node)
103{
104 MoveEvent_ptr moveEvent{static_cast<MoveEvent*>(event.release())}; //event is guaranteed to be a MoveEvent
105
106 const MoveEvent_t eventType = moveEvent->getEventType();
107 if (eventType == MOVE_EVENT_ADD_ITEM || eventType == MOVE_EVENT_REMOVE_ITEM) {
108 pugi::xml_attribute tileItemAttribute = node.attribute("tileitem");
109 if (tileItemAttribute && pugi::cast<uint16_t>(tileItemAttribute.value()) == 1) {
110 switch (eventType) {
111 case MOVE_EVENT_ADD_ITEM:
112 moveEvent->setEventType(MOVE_EVENT_ADD_ITEM_ITEMTILE);
113 break;
114 case MOVE_EVENT_REMOVE_ITEM:
115 moveEvent->setEventType(MOVE_EVENT_REMOVE_ITEM_ITEMTILE);
116 break;
117 default:
118 break;
119 }
120 }
121 }
122
123 pugi::xml_attribute attr;
124 if ((attr = node.attribute("itemid"))) {
125 int32_t id = pugi::cast<int32_t>(attr.value());
126 if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) {
127 ItemType& it = Item::items.getItemType(id);
128 it.wieldInfo = moveEvent->getWieldInfo();
129 it.minReqLevel = moveEvent->getReqLevel();
130 it.minReqMagicLevel = moveEvent->getReqMagLv();
131 it.vocationString = moveEvent->getVocationString();
132 }
133 addEvent(std::move(*moveEvent), id, itemIdMap);
134 } else if ((attr = node.attribute("fromid"))) {
135 uint32_t id = pugi::cast<uint32_t>(attr.value());
136 uint32_t endId = pugi::cast<uint32_t>(node.attribute("toid").value());
137
138 addEvent(*moveEvent, id, itemIdMap);
139
140 if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) {
141 ItemType& it = Item::items.getItemType(id);
142 it.wieldInfo = moveEvent->getWieldInfo();
143 it.minReqLevel = moveEvent->getReqLevel();
144 it.minReqMagicLevel = moveEvent->getReqMagLv();
145 it.vocationString = moveEvent->getVocationString();
146
147 while (++id <= endId) {
148 addEvent(*moveEvent, id, itemIdMap);
149
150 ItemType& tit = Item::items.getItemType(id);
151 tit.wieldInfo = moveEvent->getWieldInfo();
152 tit.minReqLevel = moveEvent->getReqLevel();
153 tit.minReqMagicLevel = moveEvent->getReqMagLv();
154 tit.vocationString = moveEvent->getVocationString();
155 }
156 } else {
157 while (++id <= endId) {
158 addEvent(*moveEvent, id, itemIdMap);
159 }
160 }
161 } else if ((attr = node.attribute("uniqueid"))) {
162 addEvent(std::move(*moveEvent), pugi::cast<int32_t>(attr.value()), uniqueIdMap);
163 } else if ((attr = node.attribute("fromuid"))) {
164 uint32_t id = pugi::cast<uint32_t>(attr.value());
165 uint32_t endId = pugi::cast<uint32_t>(node.attribute("touid").value());
166 addEvent(*moveEvent, id, uniqueIdMap);
167 while (++id <= endId) {
168 addEvent(*moveEvent, id, uniqueIdMap);
169 }
170 } else if ((attr = node.attribute("actionid"))) {
171 addEvent(std::move(*moveEvent), pugi::cast<int32_t>(attr.value()), actionIdMap);
172 } else if ((attr = node.attribute("fromaid"))) {
173 uint32_t id = pugi::cast<uint32_t>(attr.value());
174 uint32_t endId = pugi::cast<uint32_t>(node.attribute("toaid").value());
175 addEvent(*moveEvent, id, actionIdMap);
176 while (++id <= endId) {
177 addEvent(*moveEvent, id, actionIdMap);
178 }
179 } else if ((attr = node.attribute("pos"))) {
180 std::vector<int32_t> posList = vectorAtoi(explodeString(attr.as_string(), ";"));
181 if (posList.size() < 3) {
182 return false;
183 }
184
185 Position pos(posList[0], posList[1], posList[2]);
186 addEvent(std::move(*moveEvent), pos, positionMap);
187 } else {
188 return false;
189 }
190 return true;
191}
192
193bool MoveEvents::registerLuaFunction(MoveEvent* event)
194{
195 MoveEvent_ptr moveEvent{ event };
196 if (moveEvent->getItemIdRange().size() > 0) {
197 if (moveEvent->getItemIdRange().size() == 1) {
198 uint32_t id = moveEvent->getItemIdRange().at(0);
199 addEvent(*moveEvent, id, itemIdMap);
200 if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) {
201 ItemType& it = Item::items.getItemType(id);
202 it.wieldInfo = moveEvent->getWieldInfo();
203 it.minReqLevel = moveEvent->getReqLevel();
204 it.minReqMagicLevel = moveEvent->getReqMagLv();
205 it.vocationString = moveEvent->getVocationString();
206 }
207 } else {
208 uint32_t iterId = 0;
209 while (++iterId < moveEvent->getItemIdRange().size()) {
210 if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) {
211 ItemType& it = Item::items.getItemType(moveEvent->getItemIdRange().at(iterId));
212 it.wieldInfo = moveEvent->getWieldInfo();
213 it.minReqLevel = moveEvent->getReqLevel();
214 it.minReqMagicLevel = moveEvent->getReqMagLv();
215 it.vocationString = moveEvent->getVocationString();
216 }
217 addEvent(*moveEvent, moveEvent->getItemIdRange().at(iterId), itemIdMap);
218 }
219 }
220 } else {
221 return false;
222 }
223 return true;
224}
225
226bool MoveEvents::registerLuaEvent(MoveEvent* event)
227{
228 MoveEvent_ptr moveEvent{ event };
229 if (moveEvent->getItemIdRange().size() > 0) {
230 if (moveEvent->getItemIdRange().size() == 1) {
231 uint32_t id = moveEvent->getItemIdRange().at(0);
232 addEvent(*moveEvent, id, itemIdMap);
233 if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) {
234 ItemType& it = Item::items.getItemType(id);
235 it.wieldInfo = moveEvent->getWieldInfo();
236 it.minReqLevel = moveEvent->getReqLevel();
237 it.minReqMagicLevel = moveEvent->getReqMagLv();
238 it.vocationString = moveEvent->getVocationString();
239 }
240 } else {
241 auto v = moveEvent->getItemIdRange();
242 for (auto i = v.begin(); i != v.end(); i++) {
243 if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) {
244 ItemType& it = Item::items.getItemType(*i);
245 it.wieldInfo = moveEvent->getWieldInfo();
246 it.minReqLevel = moveEvent->getReqLevel();
247 it.minReqMagicLevel = moveEvent->getReqMagLv();
248 it.vocationString = moveEvent->getVocationString();
249 }
250 addEvent(*moveEvent, *i, itemIdMap);
251 }
252 }
253 } else if (moveEvent->getActionIdRange().size() > 0) {
254 if (moveEvent->getActionIdRange().size() == 1) {
255 int32_t id = moveEvent->getActionIdRange().at(0);
256 addEvent(*moveEvent, id, actionIdMap);
257 } else {
258 auto v = moveEvent->getActionIdRange();
259 for (auto i = v.begin(); i != v.end(); i++) {
260 addEvent(*moveEvent, *i, actionIdMap);
261 }
262 }
263 } else if (moveEvent->getUniqueIdRange().size() > 0) {
264 if (moveEvent->getUniqueIdRange().size() == 1) {
265 int32_t id = moveEvent->getUniqueIdRange().at(0);
266 addEvent(*moveEvent, id, uniqueIdMap);
267 } else {
268 auto v = moveEvent->getUniqueIdRange();
269 for (auto i = v.begin(); i != v.end(); i++) {
270 addEvent(*moveEvent, *i, uniqueIdMap);
271 }
272 }
273 } else if (moveEvent->getPosList().size() > 0) {
274 if (moveEvent->getPosList().size() == 1) {
275 Position pos = moveEvent->getPosList().at(0);
276 addEvent(*moveEvent, pos, positionMap);
277 } else {
278 auto v = moveEvent->getPosList();
279 for (auto i = v.begin(); i != v.end(); i++) {
280 addEvent(*moveEvent, *i, positionMap);
281 }
282 }
283 } else {
284 return false;
285 }
286
287 return true;
288}
289
290void MoveEvents::addEvent(MoveEvent moveEvent, int32_t id, MoveListMap& map)
291{
292 auto it = map.find(id);
293 if (it == map.end()) {
294 MoveEventList moveEventList;
295 moveEventList.moveEvent[moveEvent.getEventType()].push_back(std::move(moveEvent));
296 map[id] = moveEventList;
297 } else {
298 std::list<MoveEvent>& moveEventList = it->second.moveEvent[moveEvent.getEventType()];
299 for (MoveEvent& existingMoveEvent : moveEventList) {
300 if (existingMoveEvent.getSlot() == moveEvent.getSlot()) {
301 std::cout << "[Warning - MoveEvents::addEvent] Duplicate move event found: " << id << std::endl;
302 }
303 }
304 moveEventList.push_back(std::move(moveEvent));
305 }
306}
307
308MoveEvent* MoveEvents::getEvent(Item* item, MoveEvent_t eventType, slots_t slot)
309{
310 uint32_t slotp;
311 switch (slot) {
312 case CONST_SLOT_HEAD: slotp = SLOTP_HEAD; break;
313 case CONST_SLOT_NECKLACE: slotp = SLOTP_NECKLACE; break;
314 case CONST_SLOT_BACKPACK: slotp = SLOTP_BACKPACK; break;
315 case CONST_SLOT_ARMOR: slotp = SLOTP_ARMOR; break;
316 case CONST_SLOT_RIGHT: slotp = SLOTP_RIGHT; break;
317 case CONST_SLOT_LEFT: slotp = SLOTP_LEFT; break;
318 case CONST_SLOT_LEGS: slotp = SLOTP_LEGS; break;
319 case CONST_SLOT_FEET: slotp = SLOTP_FEET; break;
320 case CONST_SLOT_AMMO: slotp = SLOTP_AMMO; break;
321 case CONST_SLOT_RING: slotp = SLOTP_RING; break;
322 default: slotp = 0; break;
323 }
324
325 auto it = itemIdMap.find(item->getID());
326 if (it != itemIdMap.end()) {
327 std::list<MoveEvent>& moveEventList = it->second.moveEvent[eventType];
328 for (MoveEvent& moveEvent : moveEventList) {
329 if ((moveEvent.getSlot() & slotp) != 0) {
330 return &moveEvent;
331 }
332 }
333 }
334 return nullptr;
335}
336
337MoveEvent* MoveEvents::getEvent(Item* item, MoveEvent_t eventType)
338{
339 MoveListMap::iterator it;
340
341 if (item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) {
342 it = uniqueIdMap.find(item->getUniqueId());
343 if (it != uniqueIdMap.end()) {
344 std::list<MoveEvent>& moveEventList = it->second.moveEvent[eventType];
345 if (!moveEventList.empty()) {
346 return &(*moveEventList.begin());
347 }
348 }
349 }
350
351 if (item->hasAttribute(ITEM_ATTRIBUTE_ACTIONID)) {
352 it = actionIdMap.find(item->getActionId());
353 if (it != actionIdMap.end()) {
354 std::list<MoveEvent>& moveEventList = it->second.moveEvent[eventType];
355 if (!moveEventList.empty()) {
356 return &(*moveEventList.begin());
357 }
358 }
359 }
360
361 it = itemIdMap.find(item->getID());
362 if (it != itemIdMap.end()) {
363 std::list<MoveEvent>& moveEventList = it->second.moveEvent[eventType];
364 if (!moveEventList.empty()) {
365 return &(*moveEventList.begin());
366 }
367 }
368 return nullptr;
369}
370
371void MoveEvents::addEvent(MoveEvent moveEvent, const Position& pos, MovePosListMap& map)
372{
373 auto it = map.find(pos);
374 if (it == map.end()) {
375 MoveEventList moveEventList;
376 moveEventList.moveEvent[moveEvent.getEventType()].push_back(std::move(moveEvent));
377 map[pos] = moveEventList;
378 } else {
379 std::list<MoveEvent>& moveEventList = it->second.moveEvent[moveEvent.getEventType()];
380 if (!moveEventList.empty()) {
381 std::cout << "[Warning - MoveEvents::addEvent] Duplicate move event found: " << pos << std::endl;
382 }
383
384 moveEventList.push_back(std::move(moveEvent));
385 }
386}
387
388MoveEvent* MoveEvents::getEvent(const Tile* tile, MoveEvent_t eventType)
389{
390 auto it = positionMap.find(tile->getPosition());
391 if (it != positionMap.end()) {
392 std::list<MoveEvent>& moveEventList = it->second.moveEvent[eventType];
393 if (!moveEventList.empty()) {
394 return &(*moveEventList.begin());
395 }
396 }
397 return nullptr;
398}
399
400uint32_t MoveEvents::onCreatureMove(Creature* creature, const Tile* tile, MoveEvent_t eventType)
401{
402 const Position& pos = tile->getPosition();
403
404 uint32_t ret = 1;
405
406 MoveEvent* moveEvent = getEvent(tile, eventType);
407 if (moveEvent) {
408 ret &= moveEvent->fireStepEvent(creature, nullptr, pos);
409 }
410
411 for (size_t i = tile->getFirstIndex(), j = tile->getLastIndex(); i < j; ++i) {
412 Thing* thing = tile->getThing(i);
413 if (!thing) {
414 continue;
415 }
416
417 Item* tileItem = thing->getItem();
418 if (!tileItem) {
419 continue;
420 }
421
422 moveEvent = getEvent(tileItem, eventType);
423 if (moveEvent) {
424 ret &= moveEvent->fireStepEvent(creature, tileItem, pos);
425 }
426 }
427 return ret;
428}
429
430ReturnValue MoveEvents::onPlayerEquip(Player* player, Item* item, slots_t slot, bool isCheck)
431{
432 MoveEvent* moveEvent = getEvent(item, MOVE_EVENT_EQUIP, slot);
433 if (!moveEvent) {
434 return RETURNVALUE_NOERROR;
435 }
436 return moveEvent->fireEquip(player, item, slot, isCheck);
437}
438
439ReturnValue MoveEvents::onPlayerDeEquip(Player* player, Item* item, slots_t slot)
440{
441 MoveEvent* moveEvent = getEvent(item, MOVE_EVENT_DEEQUIP, slot);
442 if (!moveEvent) {
443 return RETURNVALUE_NOERROR;
444 }
445 return moveEvent->fireEquip(player, item, slot, false);
446}
447
448uint32_t MoveEvents::onItemMove(Item* item, Tile* tile, bool isAdd)
449{
450 MoveEvent_t eventType1, eventType2;
451 if (isAdd) {
452 eventType1 = MOVE_EVENT_ADD_ITEM;
453 eventType2 = MOVE_EVENT_ADD_ITEM_ITEMTILE;
454 } else {
455 eventType1 = MOVE_EVENT_REMOVE_ITEM;
456 eventType2 = MOVE_EVENT_REMOVE_ITEM_ITEMTILE;
457 }
458
459 uint32_t ret = 1;
460 MoveEvent* moveEvent = getEvent(tile, eventType1);
461 if (moveEvent) {
462 ret &= moveEvent->fireAddRemItem(item, nullptr, tile->getPosition());
463 }
464
465 moveEvent = getEvent(item, eventType1);
466 if (moveEvent) {
467 ret &= moveEvent->fireAddRemItem(item, nullptr, tile->getPosition());
468 }
469
470 for (size_t i = tile->getFirstIndex(), j = tile->getLastIndex(); i < j; ++i) {
471 Thing* thing = tile->getThing(i);
472 if (!thing) {
473 continue;
474 }
475
476 Item* tileItem = thing->getItem();
477 if (!tileItem || tileItem == item) {
478 continue;
479 }
480
481 moveEvent = getEvent(tileItem, eventType2);
482 if (moveEvent) {
483 ret &= moveEvent->fireAddRemItem(item, tileItem, tile->getPosition());
484 }
485 }
486 return ret;
487}
488
489MoveEvent::MoveEvent(LuaScriptInterface* interface) : Event(interface) {}
490
491std::string MoveEvent::getScriptEventName() const
492{
493 switch (eventType) {
494 case MOVE_EVENT_STEP_IN: return "onStepIn";
495 case MOVE_EVENT_STEP_OUT: return "onStepOut";
496 case MOVE_EVENT_EQUIP: return "onEquip";
497 case MOVE_EVENT_DEEQUIP: return "onDeEquip";
498 case MOVE_EVENT_ADD_ITEM: return "onAddItem";
499 case MOVE_EVENT_REMOVE_ITEM: return "onRemoveItem";
500 default:
501 std::cout << "[Error - MoveEvent::getScriptEventName] Invalid event type" << std::endl;
502 return std::string();
503 }
504}
505
506bool MoveEvent::configureEvent(const pugi::xml_node& node)
507{
508 pugi::xml_attribute eventAttr = node.attribute("event");
509 if (!eventAttr) {
510 std::cout << "[Error - MoveEvent::configureMoveEvent] Missing event" << std::endl;
511 return false;
512 }
513
514 std::string tmpStr = asLowerCaseString(eventAttr.as_string());
515 if (tmpStr == "stepin") {
516 eventType = MOVE_EVENT_STEP_IN;
517 } else if (tmpStr == "stepout") {
518 eventType = MOVE_EVENT_STEP_OUT;
519 } else if (tmpStr == "equip") {
520 eventType = MOVE_EVENT_EQUIP;
521 } else if (tmpStr == "deequip") {
522 eventType = MOVE_EVENT_DEEQUIP;
523 } else if (tmpStr == "additem") {
524 eventType = MOVE_EVENT_ADD_ITEM;
525 } else if (tmpStr == "removeitem") {
526 eventType = MOVE_EVENT_REMOVE_ITEM;
527 } else {
528 std::cout << "Error: [MoveEvent::configureMoveEvent] No valid event name " << eventAttr.as_string() << std::endl;
529 return false;
530 }
531
532 if (eventType == MOVE_EVENT_EQUIP || eventType == MOVE_EVENT_DEEQUIP) {
533 pugi::xml_attribute slotAttribute = node.attribute("slot");
534 if (slotAttribute) {
535 tmpStr = asLowerCaseString(slotAttribute.as_string());
536 if (tmpStr == "head") {
537 slot = SLOTP_HEAD;
538 } else if (tmpStr == "necklace") {
539 slot = SLOTP_NECKLACE;
540 } else if (tmpStr == "backpack") {
541 slot = SLOTP_BACKPACK;
542 } else if (tmpStr == "armor") {
543 slot = SLOTP_ARMOR;
544 } else if (tmpStr == "right-hand") {
545 slot = SLOTP_RIGHT;
546 } else if (tmpStr == "left-hand") {
547 slot = SLOTP_LEFT;
548 } else if (tmpStr == "hand" || tmpStr == "shield") {
549 slot = SLOTP_RIGHT | SLOTP_LEFT;
550 } else if (tmpStr == "legs") {
551 slot = SLOTP_LEGS;
552 } else if (tmpStr == "feet") {
553 slot = SLOTP_FEET;
554 } else if (tmpStr == "ring") {
555 slot = SLOTP_RING;
556 } else if (tmpStr == "ammo") {
557 slot = SLOTP_AMMO;
558 } else {
559 std::cout << "[Warning - MoveEvent::configureMoveEvent] Unknown slot type: " << slotAttribute.as_string() << std::endl;
560 }
561 }
562
563 wieldInfo = 0;
564
565 pugi::xml_attribute levelAttribute = node.attribute("level");
566 if (levelAttribute) {
567 reqLevel = pugi::cast<uint32_t>(levelAttribute.value());
568 if (reqLevel > 0) {
569 wieldInfo |= WIELDINFO_LEVEL;
570 }
571 }
572
573 pugi::xml_attribute magLevelAttribute = node.attribute("maglevel");
574 if (magLevelAttribute) {
575 reqMagLevel = pugi::cast<uint32_t>(magLevelAttribute.value());
576 if (reqMagLevel > 0) {
577 wieldInfo |= WIELDINFO_MAGLV;
578 }
579 }
580
581 pugi::xml_attribute premiumAttribute = node.attribute("premium");
582 if (premiumAttribute) {
583 premium = premiumAttribute.as_bool();
584 if (premium) {
585 wieldInfo |= WIELDINFO_PREMIUM;
586 }
587 }
588
589 //Gather vocation information
590 std::list<std::string> vocStringList;
591 for (auto vocationNode : node.children()) {
592 pugi::xml_attribute vocationNameAttribute = vocationNode.attribute("name");
593 if (!vocationNameAttribute) {
594 continue;
595 }
596
597 int32_t vocationId = g_vocations.getVocationId(vocationNameAttribute.as_string());
598 if (vocationId != -1) {
599 vocEquipMap[vocationId] = true;
600 if (vocationNode.attribute("showInDescription").as_bool(true)) {
601 vocStringList.push_back(asLowerCaseString(vocationNameAttribute.as_string()));
602 }
603 }
604 }
605
606 if (!vocEquipMap.empty()) {
607 wieldInfo |= WIELDINFO_VOCREQ;
608 }
609
610 for (const std::string& str : vocStringList) {
611 if (!vocationString.empty()) {
612 if (str != vocStringList.back()) {
613 vocationString.push_back(',');
614 vocationString.push_back(' ');
615 } else {
616 vocationString += " and ";
617 }
618 }
619
620 vocationString += str;
621 vocationString.push_back('s');
622 }
623 }
624 return true;
625}
626
627uint32_t MoveEvent::StepInField(Creature* creature, Item* item, const Position&)
628{
629 MagicField* field = item->getMagicField();
630 if (field) {
631 field->onStepInField(creature);
632 return 1;
633 }
634
635 return LUA_ERROR_ITEM_NOT_FOUND;
636}
637
638uint32_t MoveEvent::StepOutField(Creature*, Item*, const Position&)
639{
640 return 1;
641}
642
643uint32_t MoveEvent::AddItemField(Item* item, Item*, const Position&)
644{
645 if (MagicField* field = item->getMagicField()) {
646 Tile* tile = item->getTile();
647 if (CreatureVector* creatures = tile->getCreatures()) {
648 for (Creature* creature : *creatures) {
649 field->onStepInField(creature);
650 }
651 }
652 return 1;
653 }
654 return LUA_ERROR_ITEM_NOT_FOUND;
655}
656
657uint32_t MoveEvent::RemoveItemField(Item*, Item*, const Position&)
658{
659 return 1;
660}
661
662ReturnValue MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool isCheck)
663{
664 if (!player->hasFlag(PlayerFlag_IgnoreWeaponCheck) && moveEvent->getWieldInfo() != 0) {
665 const VocEquipMap& vocEquipMap = moveEvent->getVocEquipMap();
666 if (!vocEquipMap.empty() && vocEquipMap.find(player->getVocationId()) == vocEquipMap.end()) {
667 return RETURNVALUE_YOUDONTHAVEREQUIREDPROFESSION;
668 }
669
670 if (player->getLevel() < moveEvent->getReqLevel()) {
671 return RETURNVALUE_NOTENOUGHLEVEL;
672 }
673
674 if (player->getMagicLevel() < moveEvent->getReqMagLv()) {
675 return RETURNVALUE_NOTENOUGHMAGICLEVEL;
676 }
677
678 if (moveEvent->isPremium() && !player->isPremium()) {
679 return RETURNVALUE_YOUNEEDPREMIUMACCOUNT;
680 }
681 }
682
683 if (isCheck) {
684 return RETURNVALUE_NOERROR;
685 }
686
687 if (player->isItemAbilityEnabled(slot)) {
688 return RETURNVALUE_NOERROR;
689 }
690
691 const ItemType& it = Item::items[item->getID()];
692 if (it.transformEquipTo != 0) {
693 Item* newItem = g_game.transformItem(item, it.transformEquipTo);
694 g_game.startDecay(newItem);
695 } else {
696 player->setItemAbility(slot, true);
697 }
698
699 if (!it.abilities) {
700 return RETURNVALUE_NOERROR;
701 }
702
703 if (it.abilities->invisible) {
704 Condition* condition = Condition::createCondition(static_cast<ConditionId_t>(slot), CONDITION_INVISIBLE, -1, 0);
705 player->addCondition(condition);
706 }
707
708 if (it.abilities->manaShield) {
709 Condition* condition = Condition::createCondition(static_cast<ConditionId_t>(slot), CONDITION_MANASHIELD, -1, 0);
710 player->addCondition(condition);
711 }
712
713 if (it.abilities->speed != 0) {
714 g_game.changeSpeed(player, it.abilities->speed);
715 }
716
717 if (it.abilities->conditionSuppressions != 0) {
718 player->addConditionSuppressions(it.abilities->conditionSuppressions);
719 player->sendIcons();
720 }
721
722 if (it.abilities->regeneration) {
723 Condition* condition = Condition::createCondition(static_cast<ConditionId_t>(slot), CONDITION_REGENERATION, -1, 0);
724
725 if (it.abilities->healthGain != 0) {
726 condition->setParam(CONDITION_PARAM_HEALTHGAIN, it.abilities->healthGain);
727 }
728
729 if (it.abilities->healthTicks != 0) {
730 condition->setParam(CONDITION_PARAM_HEALTHTICKS, it.abilities->healthTicks);
731 }
732
733 if (it.abilities->manaGain != 0) {
734 condition->setParam(CONDITION_PARAM_MANAGAIN, it.abilities->manaGain);
735 }
736
737 if (it.abilities->manaTicks != 0) {
738 condition->setParam(CONDITION_PARAM_MANATICKS, it.abilities->manaTicks);
739 }
740
741 player->addCondition(condition);
742 }
743
744 //skill modifiers
745 bool needUpdateSkills = false;
746
747 for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) {
748 if (it.abilities->skills[i]) {
749 needUpdateSkills = true;
750 player->setVarSkill(static_cast<skills_t>(i), it.abilities->skills[i]);
751 }
752 }
753
754 for (int32_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) {
755 if (it.abilities->specialSkills[i]) {
756 needUpdateSkills = true;
757 player->setVarSpecialSkill(static_cast<SpecialSkills_t>(i), it.abilities->specialSkills[i]);
758 }
759 }
760
761 if (needUpdateSkills) {
762 player->sendSkills();
763 }
764
765 //stat modifiers
766 bool needUpdateStats = false;
767
768 for (int32_t s = STAT_FIRST; s <= STAT_LAST; ++s) {
769 if (it.abilities->stats[s]) {
770 needUpdateStats = true;
771 player->setVarStats(static_cast<stats_t>(s), it.abilities->stats[s]);
772 }
773
774 if (it.abilities->statsPercent[s]) {
775 needUpdateStats = true;
776 player->setVarStats(static_cast<stats_t>(s), static_cast<int32_t>(player->getDefaultStats(static_cast<stats_t>(s)) * ((it.abilities->statsPercent[s] - 100) / 100.f)));
777 }
778 }
779
780 if (needUpdateStats) {
781 player->sendStats();
782 }
783
784 return RETURNVALUE_NOERROR;
785}
786
787ReturnValue MoveEvent::DeEquipItem(MoveEvent*, Player* player, Item* item, slots_t slot, bool)
788{
789 if (!player->isItemAbilityEnabled(slot)) {
790 return RETURNVALUE_NOERROR;
791 }
792
793 player->setItemAbility(slot, false);
794
795 const ItemType& it = Item::items[item->getID()];
796 if (it.transformDeEquipTo != 0) {
797 g_game.transformItem(item, it.transformDeEquipTo);
798 g_game.startDecay(item);
799 }
800
801 if (!it.abilities) {
802 return RETURNVALUE_NOERROR;
803 }
804
805 if (it.abilities->invisible) {
806 player->removeCondition(CONDITION_INVISIBLE, static_cast<ConditionId_t>(slot));
807 }
808
809 if (it.abilities->manaShield) {
810 player->removeCondition(CONDITION_MANASHIELD, static_cast<ConditionId_t>(slot));
811 }
812
813 if (it.abilities->speed != 0) {
814 g_game.changeSpeed(player, -it.abilities->speed);
815 }
816
817 if (it.abilities->conditionSuppressions != 0) {
818 player->removeConditionSuppressions(it.abilities->conditionSuppressions);
819 player->sendIcons();
820 }
821
822 if (it.abilities->regeneration) {
823 player->removeCondition(CONDITION_REGENERATION, static_cast<ConditionId_t>(slot));
824 }
825
826 //skill modifiers
827 bool needUpdateSkills = false;
828
829 for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) {
830 if (it.abilities->skills[i] != 0) {
831 needUpdateSkills = true;
832 player->setVarSkill(static_cast<skills_t>(i), -it.abilities->skills[i]);
833 }
834 }
835
836 for (int32_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) {
837 if (it.abilities->specialSkills[i] != 0) {
838 needUpdateSkills = true;
839 player->setVarSpecialSkill(static_cast<SpecialSkills_t>(i), -it.abilities->specialSkills[i]);
840 }
841 }
842
843 if (needUpdateSkills) {
844 player->sendSkills();
845 }
846
847 //stat modifiers
848 bool needUpdateStats = false;
849
850 for (int32_t s = STAT_FIRST; s <= STAT_LAST; ++s) {
851 if (it.abilities->stats[s]) {
852 needUpdateStats = true;
853 player->setVarStats(static_cast<stats_t>(s), -it.abilities->stats[s]);
854 }
855
856 if (it.abilities->statsPercent[s]) {
857 needUpdateStats = true;
858 player->setVarStats(static_cast<stats_t>(s), -static_cast<int32_t>(player->getDefaultStats(static_cast<stats_t>(s)) * ((it.abilities->statsPercent[s] - 100) / 100.f)));
859 }
860 }
861
862 if (needUpdateStats) {
863 player->sendStats();
864 }
865
866 return RETURNVALUE_NOERROR;
867}
868
869bool MoveEvent::loadFunction(const pugi::xml_attribute& attr, bool isScripted)
870{
871 const char* functionName = attr.as_string();
872 if (strcasecmp(functionName, "onstepinfield") == 0) {
873 stepFunction = StepInField;
874 } else if (strcasecmp(functionName, "onstepoutfield") == 0) {
875 stepFunction = StepOutField;
876 } else if (strcasecmp(functionName, "onaddfield") == 0) {
877 moveFunction = AddItemField;
878 } else if (strcasecmp(functionName, "onremovefield") == 0) {
879 moveFunction = RemoveItemField;
880 } else if (strcasecmp(functionName, "onequipitem") == 0) {
881 equipFunction = EquipItem;
882 } else if (strcasecmp(functionName, "ondeequipitem") == 0) {
883 equipFunction = DeEquipItem;
884 } else {
885 if (!isScripted) {
886 std::cout << "[Warning - MoveEvent::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl;
887 return false;
888 }
889 }
890
891 if (!isScripted) {
892 scripted = false;
893 }
894 return true;
895}
896
897MoveEvent_t MoveEvent::getEventType() const
898{
899 return eventType;
900}
901
902void MoveEvent::setEventType(MoveEvent_t type)
903{
904 eventType = type;
905}
906
907uint32_t MoveEvent::fireStepEvent(Creature* creature, Item* item, const Position& pos)
908{
909 if (scripted) {
910 return executeStep(creature, item, pos);
911 } else {
912 return stepFunction(creature, item, pos);
913 }
914}
915
916bool MoveEvent::executeStep(Creature* creature, Item* item, const Position& pos)
917{
918 //onStepIn(creature, item, pos, fromPosition)
919 //onStepOut(creature, item, pos, fromPosition)
920 if (!scriptInterface->reserveScriptEnv()) {
921 std::cout << "[Error - MoveEvent::executeStep] Call stack overflow" << std::endl;
922 return false;
923 }
924
925 ScriptEnvironment* env = scriptInterface->getScriptEnv();
926 env->setScriptId(scriptId, scriptInterface);
927
928 lua_State* L = scriptInterface->getLuaState();
929
930 scriptInterface->pushFunction(scriptId);
931 LuaScriptInterface::pushUserdata<Creature>(L, creature);
932 LuaScriptInterface::setCreatureMetatable(L, -1, creature);
933 LuaScriptInterface::pushThing(L, item);
934 LuaScriptInterface::pushPosition(L, pos);
935 LuaScriptInterface::pushPosition(L, creature->getLastPosition());
936
937 return scriptInterface->callFunction(4);
938}
939
940ReturnValue MoveEvent::fireEquip(Player* player, Item* item, slots_t slot, bool isCheck)
941{
942 if (scripted) {
943 if (!equipFunction || equipFunction(this, player, item, slot, isCheck) == RETURNVALUE_NOERROR) {
944 if (executeEquip(player, item, slot, isCheck)) {
945 return RETURNVALUE_NOERROR;
946 }
947 return RETURNVALUE_CANNOTBEDRESSED;
948 }
949 return equipFunction(this, player, item, slot, isCheck);
950 } else {
951 return equipFunction(this, player, item, slot, isCheck);
952 }
953}
954
955bool MoveEvent::executeEquip(Player* player, Item* item, slots_t slot, bool isCheck)
956{
957 //onEquip(player, item, slot, isCheck)
958 //onDeEquip(player, item, slot, isCheck)
959 if (!scriptInterface->reserveScriptEnv()) {
960 std::cout << "[Error - MoveEvent::executeEquip] Call stack overflow" << std::endl;
961 return false;
962 }
963
964 ScriptEnvironment* env = scriptInterface->getScriptEnv();
965 env->setScriptId(scriptId, scriptInterface);
966
967 lua_State* L = scriptInterface->getLuaState();
968
969 scriptInterface->pushFunction(scriptId);
970 LuaScriptInterface::pushUserdata<Player>(L, player);
971 LuaScriptInterface::setMetatable(L, -1, "Player");
972 LuaScriptInterface::pushThing(L, item);
973 lua_pushnumber(L, slot);
974 LuaScriptInterface::pushBoolean(L, isCheck);
975
976 return scriptInterface->callFunction(4);
977}
978
979uint32_t MoveEvent::fireAddRemItem(Item* item, Item* tileItem, const Position& pos)
980{
981 if (scripted) {
982 return executeAddRemItem(item, tileItem, pos);
983 } else {
984 return moveFunction(item, tileItem, pos);
985 }
986}
987
988bool MoveEvent::executeAddRemItem(Item* item, Item* tileItem, const Position& pos)
989{
990 //onaddItem(moveitem, tileitem, pos)
991 //onRemoveItem(moveitem, tileitem, pos)
992 if (!scriptInterface->reserveScriptEnv()) {
993 std::cout << "[Error - MoveEvent::executeAddRemItem] Call stack overflow" << std::endl;
994 return false;
995 }
996
997 ScriptEnvironment* env = scriptInterface->getScriptEnv();
998 env->setScriptId(scriptId, scriptInterface);
999
1000 lua_State* L = scriptInterface->getLuaState();
1001
1002 scriptInterface->pushFunction(scriptId);
1003 LuaScriptInterface::pushThing(L, item);
1004 LuaScriptInterface::pushThing(L, tileItem);
1005 LuaScriptInterface::pushPosition(L, pos);
1006
1007 return scriptInterface->callFunction(3);
1008}
1009