1 /*-------------------------------------------------------------------------------
2
3 BARONY
4 File: monster_shopkeeper.cpp
5 Desc: implements all of the shopkeeper's code
6
7 Copyright 2013-2016 (c) Turning Wheel LLC, all rights reserved.
8 See LICENSE for details.
9
10 -------------------------------------------------------------------------------*/
11
12 #include "main.hpp"
13 #include "game.hpp"
14 #include "stat.hpp"
15 #include "entity.hpp"
16 #include "items.hpp"
17 #include "monster.hpp"
18 #include "sound.hpp"
19 #include "net.hpp"
20 #include "collision.hpp"
21 #include "player.hpp"
22 #include "magic/magic.hpp"
23 #include "shops.hpp"
24 #include "mod_tools.hpp"
25
initShopkeeper(Entity * my,Stat * myStats)26 void initShopkeeper(Entity* my, Stat* myStats)
27 {
28 int c;
29 node_t* node;
30
31 my->initMonster(217);
32
33 if ( multiplayer != CLIENT )
34 {
35 MONSTER_SPOTSND = -1;
36 MONSTER_SPOTVAR = 1;
37 MONSTER_IDLESND = -1;
38 MONSTER_IDLEVAR = 1;
39 }
40 if ( multiplayer != CLIENT && !MONSTER_INIT )
41 {
42 my->createPathBoundariesNPC();
43
44 for ( int x = my->monsterPathBoundaryXStart - 16; x <= my->monsterPathBoundaryXEnd + 16; x += 16 )
45 {
46 for ( int y = my->monsterPathBoundaryYStart - 16; y <= my->monsterPathBoundaryYEnd + 16; y += 16 )
47 {
48 if ( x / 16 >= 0 && x / 16 < map.width && y / 16 >= 0 && y / 16 < map.height )
49 {
50 shoparea[y / 16 + (x / 16)*map.height] = true;
51 }
52 }
53 }
54
55 if ( myStats != NULL )
56 {
57 if ( !myStats->leader_uid )
58 {
59 myStats->leader_uid = 0;
60 }
61
62 if ( !strcmp(myStats->name, "") )
63 {
64 strcpy(myStats->name, language[158 + rand() % 26]);
65 }
66
67 // apply random stat increases if set in stat_shared.cpp or editor
68 setRandomMonsterStats(myStats);
69
70 if ( currentlevel >= 25 && myStats->HP == 300 && myStats->MAXHP == 300 )
71 {
72 myStats->HP *= 2;
73 myStats->MAXHP *= 2;
74 myStats->OLDHP = myStats->HP;
75 }
76
77 // generate 6 items max, less if there are any forced items from boss variants
78 int customItemsToGenerate = ITEM_CUSTOM_SLOT_LIMIT;
79
80 // boss variants
81
82 // random effects
83 if ( rand() % 20 == 0 )
84 {
85 myStats->EFFECTS[EFF_ASLEEP] = true;
86 myStats->EFFECTS_TIMERS[EFF_ASLEEP] = 1800 + rand() % 3600;
87 }
88
89 // generates equipment and weapons if available from editor
90 createMonsterEquipment(myStats);
91
92 // create any custom inventory items from editor if available
93 createCustomInventory(myStats, customItemsToGenerate);
94
95 // count if any custom inventory items from editor
96 int customItems = countCustomItems(myStats); //max limit of 6 custom items per entity.
97
98 // count any inventory items set to default in edtior
99 int defaultItems = countDefaultItems(myStats);
100
101 my->setHardcoreStats(*myStats);
102
103 // generate the default inventory items for the monster, provided the editor sprite allowed enough default slots
104 switch ( defaultItems )
105 {
106 case 6:
107 case 5:
108 case 4:
109 case 3:
110 case 2:
111 case 1:
112 break;
113 default:
114 break;
115 }
116
117 //give weapon
118 if ( myStats->weapon == NULL && myStats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] == 1 )
119 {
120 if ( currentlevel < 25 )
121 {
122 myStats->weapon = newItem(SPELLBOOK_MAGICMISSILE, EXCELLENT, 0, 1, 0, false, NULL);
123 }
124 else
125 {
126 if ( rand() % 2 == 0 )
127 {
128 myStats->weapon = newItem(SPELLBOOK_DRAIN_SOUL, EXCELLENT, 0, 1, 0, false, NULL);
129 }
130 else
131 {
132 myStats->weapon = newItem(SPELLBOOK_BLEED, EXCELLENT, 0, 1, 0, false, NULL);
133 }
134 }
135 }
136
137 // give shopkeeper items
138 if ( myStats->MISC_FLAGS[STAT_FLAG_NPC] == 14 )
139 {
140 myStats->MISC_FLAGS[STAT_FLAG_MYSTERIOUS_SHOPKEEP] = 1;
141 my->monsterStoreType = 10;
142 }
143 else if ( myStats->MISC_FLAGS[STAT_FLAG_NPC] > 0 )
144 {
145 my->monsterStoreType = myStats->MISC_FLAGS[STAT_FLAG_NPC] - 1;
146 if ( my->monsterStoreType > 9 )
147 {
148 my->monsterStoreType = rand() % 9;
149 if ( my->monsterStoreType == 8 )
150 {
151 my->monsterStoreType++;
152 }
153 }
154 }
155 else
156 {
157 my->monsterStoreType = rand() % 10;
158 }
159 int numitems = 10 + rand() % 5;
160 int blessedShopkeeper = 1; // bless important pieces of gear like armor, jewelry, weapons..
161 if ( currentlevel >= 30 )
162 {
163 if ( rand() % 3 == 0 )
164 {
165 blessedShopkeeper = 3;
166 }
167 else
168 {
169 blessedShopkeeper = 2;
170 }
171 }
172 else if ( currentlevel >= 25 )
173 {
174 if ( rand() % 4 == 0 )
175 {
176 blessedShopkeeper = 3;
177 }
178 else
179 {
180 blessedShopkeeper = 2;
181 }
182 }
183 else if ( currentlevel >= 18 )
184 {
185 if ( rand() % 3 == 0 )
186 {
187 blessedShopkeeper = 2;
188 }
189 }
190
191 int customShopkeeperInUse = ((myStats->MISC_FLAGS[STAT_FLAG_SHOPKEEPER_CUSTOM_PROPERTIES] >> 12) & 0xF);
192 int oldMonsterStoreType = my->monsterStoreType;
193 if ( customShopkeeperInUse == MonsterStatCustomManager::StatEntry::ShopkeeperCustomFlags::ENABLE_GEN_ITEMS )
194 {
195 if ( (myStats->MISC_FLAGS[STAT_FLAG_SHOPKEEPER_CUSTOM_PROPERTIES] & 0xFF) > 1 )
196 {
197 numitems = (myStats->MISC_FLAGS[STAT_FLAG_SHOPKEEPER_CUSTOM_PROPERTIES] & 0xFF) - 1;
198 }
199 if ( ((myStats->MISC_FLAGS[STAT_FLAG_SHOPKEEPER_CUSTOM_PROPERTIES] >> 8) & 0xF) > 0 )
200 {
201 blessedShopkeeper = std::max(1, (myStats->MISC_FLAGS[STAT_FLAG_SHOPKEEPER_CUSTOM_PROPERTIES] >> 8) & 0xF);
202 }
203 }
204 else if ( customShopkeeperInUse == MonsterStatCustomManager::StatEntry::ShopkeeperCustomFlags::DISABLE_GEN_ITEMS )
205 {
206 my->monsterStoreType = -1;
207 }
208
209 bool sellVampireBlood = false;
210 for ( c = 0; c < MAXPLAYERS; ++c )
211 {
212 if ( players[c] && players[c]->entity )
213 {
214 if ( players[c]->entity->playerRequiresBloodToSustain() )
215 {
216 sellVampireBlood = true;
217 break;
218 }
219 }
220 }
221
222 Item* tmpItem = nullptr;
223 bool doneAlembic = false;
224 bool doneLockpick = false;
225 bool doneBackpack = false;
226 bool doneTinkeringKit = false;
227 bool doneFeather = false;
228 switch ( my->monsterStoreType )
229 {
230 case -1:
231 my->monsterStoreType = oldMonsterStoreType; // don't generate any items.
232 break;
233 case 0:
234 // arms & armor store
235 if ( blessedShopkeeper > 0 )
236 {
237 numitems += rand() % 5; // offset some of the quantity reduction.
238 }
239 for ( c = 0; c < numitems; c++ )
240 {
241 if ( currentlevel >= 18 )
242 {
243 if ( rand() % 2 )
244 {
245 if ( rand() % 10 == 0 )
246 {
247 tmpItem = newItem(itemLevelCurve(THROWN, 8, currentlevel), static_cast<Status>(SERVICABLE + rand() % 2), 0, 3 + rand() % 3, rand(), false, &myStats->inventory);
248 }
249 else
250 {
251 tmpItem = newItem(itemLevelCurve(ARMOR, 5, currentlevel), static_cast<Status>(WORN + rand() % 3), rand() % blessedShopkeeper, 1 + rand() % 4, rand(), false, &myStats->inventory);
252 }
253 }
254 else
255 {
256 tmpItem = newItem(itemLevelCurve(WEAPON, 10, currentlevel), static_cast<Status>(WORN + rand() % 3), rand() % blessedShopkeeper, 1 + rand() % 4, rand(), false, &myStats->inventory);
257 }
258 }
259 else
260 {
261 if ( rand() % 2 )
262 {
263 if ( rand() % 8 == 0 )
264 {
265 tmpItem = newItem(itemLevelCurve(THROWN, 0, currentlevel + 20), static_cast<Status>(WORN + rand() % 3), 0, 3 + rand() % 3, rand(), false, &myStats->inventory);
266 }
267 else
268 {
269 tmpItem = newItem(static_cast<ItemType>(rand() % 20), static_cast<Status>(WORN + rand() % 3), 0, 1 + rand() % 4, rand(), false, &myStats->inventory);
270 }
271 }
272 else
273 {
274 int i = rand() % 23;
275 if ( i < 18 )
276 {
277 tmpItem = newItem(static_cast<ItemType>(GLOVES + i), static_cast<Status>(WORN + rand() % 3), 0, 1 + rand() % 4, rand(), false, &myStats->inventory);
278 }
279 else if ( i < 21 )
280 {
281 tmpItem = newItem(static_cast<ItemType>(GLOVES + i + 4), static_cast<Status>(WORN + rand() % 3), 0, 1 + rand() % 6, rand(), false, &myStats->inventory);
282 }
283 else
284 {
285 // punching armaments
286 tmpItem = newItem(static_cast<ItemType>(BRASS_KNUCKLES + rand() % 3), static_cast<Status>(WORN + rand() % 3), 0, 1 + rand() % 2, rand(), false, &myStats->inventory);
287 }
288 }
289 }
290 // post-processing
291 if ( tmpItem )
292 {
293 if ( tmpItem->beatitude > 0 )
294 {
295 tmpItem->count = 1;
296 tmpItem->status = static_cast<Status>(SERVICABLE + rand() % 2);
297 }
298 if ( tmpItem->type >= BRONZE_TOMAHAWK && tmpItem->type <= CRYSTAL_SHURIKEN )
299 {
300 // thrown weapons always fixed status. (tomahawk = decrepit, shuriken = excellent)
301 tmpItem->status = std::min(static_cast<Status>(DECREPIT + (tmpItem->type - BRONZE_TOMAHAWK)), EXCELLENT);
302 }
303 }
304 }
305 break;
306 case 1:
307 // hat store
308 for ( c = 0; c < numitems; c++ )
309 {
310 tmpItem = newItem(static_cast<ItemType>(HAT_PHRYGIAN + rand() % 7), static_cast<Status>(WORN + rand() % 3), rand() % blessedShopkeeper, 1 + rand() % 6, rand(), false, &myStats->inventory);
311 // post-processing
312 if ( tmpItem && tmpItem->beatitude > 0 )
313 {
314 tmpItem->count = 1;
315 tmpItem->status = static_cast<Status>(SERVICABLE + rand() % 2);
316 }
317 }
318 break;
319 case 2:
320 // jewelry store
321 for ( c = 0; c < numitems; c++ )
322 {
323 switch ( rand() % 3 )
324 {
325 case 0:
326 tmpItem = newItem(itemLevelCurve(AMULET, 0, currentlevel + 5), static_cast<Status>(WORN + rand() % 3), rand() % blessedShopkeeper, 1 + rand() % 2, rand(), false, &myStats->inventory);
327 break;
328 case 1:
329 tmpItem = newItem(itemLevelCurve(RING, 0, currentlevel + 5), static_cast<Status>(WORN + rand() % 3), rand() % blessedShopkeeper, 1 + rand() % 2, rand(), false, &myStats->inventory);
330 break;
331 case 2:
332 tmpItem = newItem(static_cast<ItemType>(GEM_GARNET + rand() % 16), static_cast<Status>(WORN + rand() % 3), 0, 1 + rand() % 2, rand(), false, &myStats->inventory);
333 break;
334 }
335 // post-processing
336 if ( tmpItem && tmpItem->beatitude > 0 )
337 {
338 tmpItem->count = 1;
339 tmpItem->status = static_cast<Status>(SERVICABLE + rand() % 2);
340 }
341 }
342 break;
343 case 3:
344 // bookstore
345 for ( c = 0; c < numitems; c++ )
346 {
347 switch ( rand() % 3 )
348 {
349 case 0:
350 if ( currentlevel >= 18 )
351 {
352 tmpItem = newItem(itemLevelCurve(SPELLBOOK, 0, currentlevel), static_cast<Status>(WORN + rand() % 3), rand() % blessedShopkeeper, 1 + rand() % 2, rand(), true, &myStats->inventory);
353 }
354 else
355 {
356 tmpItem = newItem(static_cast<ItemType>(SPELLBOOK_FORCEBOLT + rand() % 21), static_cast<Status>(WORN + rand() % 3), 0, 1 + rand() % 2, rand(), true, &myStats->inventory);
357 }
358 break;
359 case 1:
360 tmpItem = newItem(itemLevelCurve(SCROLL, 0, 35), static_cast<Status>(WORN + rand() % 3), 0, 1 + rand() % 2, rand(), true, &myStats->inventory);
361 break;
362 case 2:
363 if ( rand() % 3 == 0 )
364 {
365 tmpItem = newItem(itemLevelCurve(SCROLL, 0, 35), static_cast<Status>(WORN + rand() % 3), 0, 1 + rand() % 2, rand(), true, &myStats->inventory);
366 }
367 else
368 {
369 tmpItem = newItem(READABLE_BOOK, static_cast<Status>(WORN + rand() % 3), 0, 1 + rand() % 3, rand(), false, &myStats->inventory);
370 }
371 break;
372 }
373 // post-processing
374 if ( rand() % blessedShopkeeper > 0 )
375 {
376 tmpItem->status = static_cast<Status>(SERVICABLE + rand() % 2);
377 }
378 }
379 if ( !doneFeather && rand() % 20 == 0 )
380 {
381 if ( rand() % 5 == 0 )
382 {
383 newItem(ENCHANTED_FEATHER, EXCELLENT, 0, 1, ENCHANTED_FEATHER_MAX_DURABILITY - 1, true, &myStats->inventory);
384 }
385 else
386 {
387 newItem(ENCHANTED_FEATHER, SERVICABLE, 0, 1, (3 * (ENCHANTED_FEATHER_MAX_DURABILITY - 1)) / 4, true, &myStats->inventory);
388 }
389 tmpItem = newItem(SCROLL_BLANK, static_cast<Status>(WORN + rand() % 3), 0, 1 + rand() % 3, rand(), true, &myStats->inventory);
390 doneFeather = true;
391 }
392 break;
393 case 4:
394 // apothecary
395 for ( c = 0; c < numitems; c++ )
396 {
397 if ( !doneAlembic && rand() % 2 == 0 )
398 {
399 if ( rand() % 2 == 0 )
400 {
401 tmpItem = newItem(TOOL_ALEMBIC, static_cast<Status>(WORN + rand() % 3), 0, 1, rand(), true, &myStats->inventory);
402 if ( rand() % blessedShopkeeper > 0 )
403 {
404 tmpItem->status = static_cast<Status>(SERVICABLE + rand() % 2);
405 }
406 }
407 if ( rand() % 2 == 0 )
408 {
409 tmpItem = newItem(TOOL_ALEMBIC, static_cast<Status>(WORN + rand() % 3), 0, 1, rand(), true, &myStats->inventory);
410 if ( rand() % blessedShopkeeper > 0 )
411 {
412 tmpItem->status = static_cast<Status>(SERVICABLE + rand() % 2);
413 }
414 }
415 tmpItem = newItem(TOOL_ALEMBIC, static_cast<Status>(WORN + rand() % 3), 0, 1, rand(), true, &myStats->inventory);
416 doneAlembic = true;
417 }
418 else
419 {
420 tmpItem = newItem(static_cast<ItemType>(POTION_WATER + rand() % 15), static_cast<Status>(WORN + rand() % 3), 0, 1 + rand() % 5, rand(), true, &myStats->inventory);
421 }
422 // post-processing
423 if ( rand() % blessedShopkeeper > 0 )
424 {
425 tmpItem->status = static_cast<Status>(SERVICABLE + rand() % 2);
426 }
427 }
428 newItem(POTION_EMPTY, SERVICABLE, 0, 2 + rand() % 5, 0, true, &myStats->inventory);
429 if ( sellVampireBlood )
430 {
431 tmpItem = newItem(FOOD_BLOOD, EXCELLENT, 0, 2 + rand() % 3, rand(), false, &myStats->inventory);
432 }
433 break;
434 case 5:
435 // staff shop
436 for ( c = 0; c < numitems; c++ )
437 {
438 if ( currentlevel >= 18 )
439 {
440 tmpItem = newItem(itemLevelCurve(MAGICSTAFF, 0, currentlevel), static_cast<Status>(WORN + rand() % 3), 0, 1, rand(), true, &myStats->inventory);
441 }
442 else
443 {
444 tmpItem = newItem(itemLevelCurve(MAGICSTAFF, 0, 15), static_cast<Status>(WORN + rand() % 3), 0, 1, rand(), true, &myStats->inventory);
445 }
446 // post-processing
447 if ( rand() % blessedShopkeeper > 0 )
448 {
449 tmpItem->status = static_cast<Status>(SERVICABLE + rand() % 2);
450 }
451 }
452 break;
453 case 6:
454 // food store
455 for ( c = 0; c < numitems; c++ )
456 {
457 tmpItem = newItem(static_cast<ItemType>(FOOD_BREAD + rand() % 7), static_cast<Status>(SERVICABLE + rand() % 2), 0, 1 + rand() % 3, rand(), false, &myStats->inventory);
458 // post-processing
459 if ( rand() % blessedShopkeeper > 0 )
460 {
461 tmpItem->status = static_cast<Status>(SERVICABLE + rand() % 2);
462 }
463 }
464 break;
465 case 7:
466 // hardware store
467 for ( c = 0; c < numitems; c++ )
468 {
469 if ( rand() % 20 == 0 )
470 {
471 tmpItem = newItem(itemLevelCurve(THROWN, 0, currentlevel + 20), static_cast<Status>(SERVICABLE + rand() % 2), 0, 3 + rand() % 3, rand(), false, &myStats->inventory);
472 }
473 else
474 {
475 tmpItem = newItem(static_cast<ItemType>(TOOL_PICKAXE + rand() % 11), static_cast<Status>(WORN + rand() % 3), 0, 1 + rand() % 3, rand(), false, &myStats->inventory);
476 }
477 // post-processing
478 if ( rand() % blessedShopkeeper > 0 )
479 {
480 tmpItem->status = static_cast<Status>(SERVICABLE + rand() % 2);
481 }
482 if ( tmpItem->type >= BRONZE_TOMAHAWK && tmpItem->type <= CRYSTAL_SHURIKEN )
483 {
484 // thrown weapons always fixed status. (tomahawk = decrepit, shuriken = excellent)
485 tmpItem->status = std::min(static_cast<Status>(DECREPIT + (tmpItem->type - BRONZE_TOMAHAWK)), EXCELLENT);
486 }
487
488 if ( !doneLockpick && rand() % 2 == 0 )
489 {
490 tmpItem = newItem(TOOL_LOCKPICK, static_cast<Status>(WORN + rand() % 3), 0, 1 + rand() % 3, rand(), true, &myStats->inventory);
491 if ( rand() % blessedShopkeeper > 0 )
492 {
493 tmpItem->status = static_cast<Status>(SERVICABLE + rand() % 2);
494 }
495 doneLockpick = true;
496 }
497
498 if ( !doneTinkeringKit && rand() % 5 == 0 )
499 {
500 newItem(TOOL_TINKERING_KIT, DECREPIT, 0, 1, rand(), true, &myStats->inventory);
501 doneTinkeringKit = true;
502 }
503
504 if ( !doneAlembic && rand() % 2 == 0 )
505 {
506 tmpItem = newItem(TOOL_ALEMBIC, static_cast<Status>(WORN + rand() % 3), 0, 1, rand(), true, &myStats->inventory);
507 if ( rand() % blessedShopkeeper > 0 )
508 {
509 tmpItem->status = static_cast<Status>(SERVICABLE + rand() % 2);
510 }
511 if ( rand() % 2 == 0 )
512 {
513 tmpItem = newItem(TOOL_ALEMBIC, static_cast<Status>(WORN + rand() % 3), 0, 1, rand(), true, &myStats->inventory);
514 if ( rand() % blessedShopkeeper > 0 )
515 {
516 tmpItem->status = static_cast<Status>(SERVICABLE + rand() % 2);
517 }
518 }
519 if ( rand() % 2 == 0 )
520 {
521 tmpItem = newItem(TOOL_ALEMBIC, static_cast<Status>(WORN + rand() % 3), 0, 1, rand(), true, &myStats->inventory);
522 if ( rand() % blessedShopkeeper > 0 )
523 {
524 tmpItem->status = static_cast<Status>(SERVICABLE + rand() % 2);
525 }
526 }
527 doneAlembic = true;
528 }
529
530 }
531 if ( !doneBackpack && rand() % 10 == 0 )
532 {
533 newItem(CLOAK_BACKPACK, static_cast<Status>(WORN + rand() % 3), 0, 1, rand(), true, &myStats->inventory);
534 doneBackpack = true;
535 }
536 break;
537 case 8:
538 // weapon/hunting store
539 if ( currentlevel < 10 && customShopkeeperInUse == 0 )
540 {
541 numitems = 7 + rand() % 4;
542 }
543 for ( c = 0; c < numitems; c++ )
544 {
545 switch ( rand() % 20 )
546 {
547 case 0:
548 case 1:
549 case 2:
550 case 3:
551 {
552 // ranged weapons
553 std::vector<ItemType> rangedWeapons;
554 rangedWeapons.push_back(SHORTBOW);
555 if ( currentlevel < 5 )
556 {
557 rangedWeapons.push_back(SLING);
558 }
559 if ( currentlevel >= 8 )
560 {
561 rangedWeapons.push_back(CROSSBOW);
562 }
563 if ( currentlevel >= 13 )
564 {
565 rangedWeapons.push_back(LONGBOW);
566 }
567 if ( currentlevel >= 15 )
568 {
569 rangedWeapons.push_back(HEAVY_CROSSBOW);
570 rangedWeapons.push_back(COMPOUND_BOW);
571 }
572 ItemType chosenType = rangedWeapons[rand() % rangedWeapons.size()];
573 tmpItem = newItem(chosenType, static_cast<Status>(WORN + rand() % 3), rand() % blessedShopkeeper, 1, rand(), false, &myStats->inventory);
574 break;
575 }
576 case 4:
577 case 5:
578 case 6:
579 case 7:
580 case 8:
581 case 9:
582 case 10:
583 case 11:
584 // standard weapons
585 if ( currentlevel >= 18 )
586 {
587 tmpItem = newItem(itemLevelCurve(WEAPON, 10, currentlevel + 5), static_cast<Status>(WORN + rand() % 3), rand() % blessedShopkeeper, 1, rand(), false, &myStats->inventory);
588 }
589 else
590 {
591 tmpItem = newItem(itemLevelCurve(WEAPON, 0, currentlevel + 5), static_cast<Status>(WORN + rand() % 3), rand() % blessedShopkeeper, 1, rand(), false, &myStats->inventory);
592 }
593 break;
594 case 12:
595 case 13:
596 // thrown weapons (10%), sometime punching things
597 if ( rand() % 10 == 0 )
598 {
599 // punching stuff (5%)
600 std::vector<ItemType> gloveWeapons;
601 gloveWeapons.push_back(BRASS_KNUCKLES);
602 if ( currentlevel >= items[SPIKED_GAUNTLETS].level )
603 {
604 gloveWeapons.push_back(SPIKED_GAUNTLETS);
605 }
606 if ( currentlevel >= items[IRON_KNUCKLES].level )
607 {
608 gloveWeapons.push_back(IRON_KNUCKLES);
609 }
610 ItemType chosenType = gloveWeapons[rand() % gloveWeapons.size()];
611 tmpItem = newItem(chosenType, static_cast<Status>(WORN + rand() % 3), rand() % blessedShopkeeper, 1, rand(), false, &myStats->inventory);
612 }
613 else
614 {
615 if ( currentlevel >= 18 )
616 {
617 tmpItem = newItem(itemLevelCurve(THROWN, 0, currentlevel + 20), static_cast<Status>(WORN + rand() % 3), 0, 3 + rand() % 3, rand(), false, &myStats->inventory);
618 }
619 else
620 {
621 tmpItem = newItem(itemLevelCurve(THROWN, 0, 8), static_cast<Status>(SERVICABLE + rand() % 2), 0, 3 + rand() % 3, rand(), false, &myStats->inventory);
622 }
623 }
624 break;
625 case 14:
626 case 15:
627 case 16:
628 case 17:
629 case 18:
630 case 19:
631 {
632 // quivers (30%)
633 std::vector<ItemType> quivers;
634 quivers.push_back(QUIVER_SILVER);
635 quivers.push_back(QUIVER_LIGHTWEIGHT);
636 if ( currentlevel >= 5 )
637 {
638 quivers.push_back(QUIVER_KNOCKBACK);
639 }
640 if ( currentlevel >= 10 )
641 {
642 quivers.push_back(QUIVER_FIRE);
643 quivers.push_back(QUIVER_HUNTING);
644 }
645 if ( currentlevel >= 18 )
646 {
647 quivers.push_back(QUIVER_PIERCE);
648 quivers.push_back(QUIVER_CRYSTAL);
649 }
650 ItemType chosenType = quivers[rand() % quivers.size()];
651 tmpItem = newItem(chosenType, EXCELLENT, 0, 10 + rand() % 6, 0, true, &myStats->inventory); // 10-15 arrows.
652 break;
653 }
654 default:
655 break;
656 }
657 // post-processing
658 if ( tmpItem )
659 {
660 if ( tmpItem->beatitude > 0 )
661 {
662 tmpItem->count = 1;
663 tmpItem->status = static_cast<Status>(SERVICABLE + rand() % 2);
664 }
665 if ( tmpItem->type >= BRONZE_TOMAHAWK && tmpItem->type <= CRYSTAL_SHURIKEN )
666 {
667 // thrown weapons always fixed status. (tomahawk = decrepit, shuriken = excellent)
668 tmpItem->status = std::min(static_cast<Status>(DECREPIT + (tmpItem->type - BRONZE_TOMAHAWK)), EXCELLENT);
669 }
670 }
671 }
672 break;
673 case 9:
674 // general store
675 for ( c = 0; c < numitems; c++ )
676 {
677 Category cat = static_cast<Category>(rand() % (NUMCATEGORIES - 1));
678 tmpItem = newItem(itemLevelCurve(cat, 0, currentlevel + 5), static_cast<Status>(WORN + rand() % 3), 0, 1 + rand() % 3, rand(), false, &myStats->inventory);
679 if ( tmpItem && (itemCategory(tmpItem) == WEAPON || itemCategory(tmpItem) == ARMOR || itemCategory(tmpItem) == RING || itemCategory(tmpItem) == AMULET) )
680 {
681 tmpItem->beatitude += rand() % blessedShopkeeper;
682 // post-processing
683 if ( tmpItem->beatitude > 0 )
684 {
685 tmpItem->count = 1;
686 }
687 }
688 if ( tmpItem && tmpItem->type >= BRONZE_TOMAHAWK && tmpItem->type <= CRYSTAL_SHURIKEN )
689 {
690 // thrown weapons always fixed status. (tomahawk = decrepit, shuriken = excellent)
691 tmpItem->status = std::min(static_cast<Status>(DECREPIT + (tmpItem->type - BRONZE_TOMAHAWK)), EXCELLENT);
692 }
693 }
694 if ( !doneTinkeringKit && rand() % 20 == 0 )
695 {
696 if ( rand() % 5 == 0 )
697 {
698 newItem(TOOL_TINKERING_KIT, WORN, 0, 1, rand(), true, &myStats->inventory);
699 }
700 else
701 {
702 newItem(TOOL_TINKERING_KIT, DECREPIT, 0, 1, rand(), true, &myStats->inventory);
703 }
704 doneTinkeringKit = true;
705 }
706 if ( sellVampireBlood )
707 {
708 tmpItem = newItem(FOOD_BLOOD, EXCELLENT, 0, 1 + rand() % 4, rand(), false, &myStats->inventory);
709 }
710 break;
711 case 10:
712 // mysterious merchant.
713 // general store
714 numitems = 15;
715 while ( numitems > 0 )
716 {
717 for ( auto orbCategories : shopkeeperMysteriousItems )
718 {
719 for (auto itemInCategory : orbCategories.second)
720 {
721 if (itemInCategory == ENCHANTED_FEATHER)
722 {
723 newItem(static_cast<ItemType>(itemInCategory), EXCELLENT, 0, 1, ENCHANTED_FEATHER_MAX_DURABILITY - 1, true, &myStats->inventory);
724 }
725 else if (itemTypeIsQuiver(static_cast<ItemType>(itemInCategory)))
726 {
727 newItem(static_cast<ItemType>(itemInCategory), SERVICABLE, 0, 50, ITEM_GENERATED_QUIVER_APPEARANCE, true, &myStats->inventory);
728 }
729 else
730 {
731 int bless = 0;
732 Status status = SERVICABLE;
733 if (itemInCategory >= CRYSTAL_SWORD && itemInCategory <= CRYSTAL_MACE)
734 {
735 bless = 3;
736 status = EXCELLENT;
737 }
738 else if (itemInCategory >= ARTIFACT_SWORD && itemInCategory <= ARTIFACT_BOW)
739 {
740 status = SERVICABLE;
741 }
742 newItem(static_cast<ItemType>(itemInCategory), status, bless, 1, rand(), true, &myStats->inventory);
743 }
744 --numitems;
745 }
746 }
747 break;
748 }
749 break;
750 default:
751 break;
752 }
753 }
754 }
755
756 // torso
757 Entity* entity = newEntity(218, 0, map.entities, nullptr); //Limb entity.
758 entity->sizex = 4;
759 entity->sizey = 4;
760 entity->skill[2] = my->getUID();
761 entity->flags[PASSABLE] = true;
762 entity->flags[NOUPDATE] = true;
763 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
764 entity->focalx = limbs[SHOPKEEPER][1][0]; // 0
765 entity->focaly = limbs[SHOPKEEPER][1][1]; // 0
766 entity->focalz = limbs[SHOPKEEPER][1][2]; // 0
767 entity->behavior = &actShopkeeperLimb;
768 entity->parent = my->getUID();
769 node = list_AddNodeLast(&my->children);
770 node->element = entity;
771 node->deconstructor = &emptyDeconstructor;
772 node->size = sizeof(Entity*);
773 my->bodyparts.push_back(entity);
774
775 // right leg
776 entity = newEntity(222, 0, map.entities, nullptr); //Limb entity.
777 entity->sizex = 4;
778 entity->sizey = 4;
779 entity->skill[2] = my->getUID();
780 entity->flags[PASSABLE] = true;
781 entity->flags[NOUPDATE] = true;
782 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
783 entity->focalx = limbs[SHOPKEEPER][2][0]; // 0
784 entity->focaly = limbs[SHOPKEEPER][2][1]; // 0
785 entity->focalz = limbs[SHOPKEEPER][2][2]; // 2
786 entity->behavior = &actShopkeeperLimb;
787 entity->parent = my->getUID();
788 node = list_AddNodeLast(&my->children);
789 node->element = entity;
790 node->deconstructor = &emptyDeconstructor;
791 node->size = sizeof(Entity*);
792 my->bodyparts.push_back(entity);
793
794 // left leg
795 entity = newEntity(221, 0, map.entities, nullptr); //Limb entity.
796 entity->sizex = 4;
797 entity->sizey = 4;
798 entity->skill[2] = my->getUID();
799 entity->flags[PASSABLE] = true;
800 entity->flags[NOUPDATE] = true;
801 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
802 entity->focalx = limbs[SHOPKEEPER][3][0]; // 0
803 entity->focaly = limbs[SHOPKEEPER][3][1]; // 0
804 entity->focalz = limbs[SHOPKEEPER][3][2]; // 2
805 entity->behavior = &actShopkeeperLimb;
806 entity->parent = my->getUID();
807 node = list_AddNodeLast(&my->children);
808 node->element = entity;
809 node->deconstructor = &emptyDeconstructor;
810 node->size = sizeof(Entity*);
811 my->bodyparts.push_back(entity);
812
813 // right arm
814 entity = newEntity(220, 0, map.entities, nullptr); //Limb entity.
815 entity->sizex = 4;
816 entity->sizey = 4;
817 entity->skill[2] = my->getUID();
818 entity->flags[PASSABLE] = true;
819 entity->flags[NOUPDATE] = true;
820 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
821 entity->focalx = limbs[SHOPKEEPER][4][0]; // 0
822 entity->focaly = limbs[SHOPKEEPER][4][1]; // 0
823 entity->focalz = limbs[SHOPKEEPER][4][2]; // 1.5
824 entity->behavior = &actShopkeeperLimb;
825 entity->parent = my->getUID();
826 node = list_AddNodeLast(&my->children);
827 node->element = entity;
828 node->deconstructor = &emptyDeconstructor;
829 node->size = sizeof(Entity*);
830 my->bodyparts.push_back(entity);
831
832 // left arm
833 entity = newEntity(219, 0, map.entities, nullptr); //Limb entity.
834 entity->sizex = 4;
835 entity->sizey = 4;
836 entity->skill[2] = my->getUID();
837 entity->flags[PASSABLE] = true;
838 entity->flags[NOUPDATE] = true;
839 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
840 entity->focalx = limbs[SHOPKEEPER][5][0]; // 0
841 entity->focaly = limbs[SHOPKEEPER][5][1]; // 0
842 entity->focalz = limbs[SHOPKEEPER][5][2]; // 1.5
843 entity->behavior = &actShopkeeperLimb;
844 entity->parent = my->getUID();
845 node = list_AddNodeLast(&my->children);
846 node->element = entity;
847 node->deconstructor = &emptyDeconstructor;
848 node->size = sizeof(Entity*);
849 my->bodyparts.push_back(entity);
850
851 // world weapon
852 entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
853 entity->sizex = 4;
854 entity->sizey = 4;
855 entity->skill[2] = my->getUID();
856 entity->flags[PASSABLE] = true;
857 entity->flags[NOUPDATE] = true;
858 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
859 entity->focalx = limbs[SHOPKEEPER][6][0]; // 1.5
860 entity->focaly = limbs[SHOPKEEPER][6][1]; // 0
861 entity->focalz = limbs[SHOPKEEPER][6][2]; // -.5
862 entity->behavior = &actShopkeeperLimb;
863 entity->parent = my->getUID();
864 entity->pitch = .25;
865 node = list_AddNodeLast(&my->children);
866 node->element = entity;
867 node->deconstructor = &emptyDeconstructor;
868 node->size = sizeof(Entity*);
869 my->bodyparts.push_back(entity);
870
871 // shield
872 entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
873 entity->sizex = 4;
874 entity->sizey = 4;
875 entity->skill[2] = my->getUID();
876 entity->flags[PASSABLE] = true;
877 entity->flags[NOUPDATE] = true;
878 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
879 entity->focalx = limbs[SHOPKEEPER][7][0]; // 2
880 entity->focaly = limbs[SHOPKEEPER][7][1]; // 0
881 entity->focalz = limbs[SHOPKEEPER][7][2]; // 0
882 entity->behavior = &actShopkeeperLimb;
883 entity->parent = my->getUID();
884 node = list_AddNodeLast(&my->children);
885 node->element = entity;
886 node->deconstructor = &emptyDeconstructor;
887 node->size = sizeof(Entity*);
888 my->bodyparts.push_back(entity);
889
890 // cloak
891 entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
892 entity->sizex = 4;
893 entity->sizey = 4;
894 entity->skill[2] = my->getUID();
895 entity->scalex = 1.01;
896 entity->scaley = 1.01;
897 entity->scalez = 1.01;
898 entity->flags[PASSABLE] = true;
899 entity->flags[NOUPDATE] = true;
900 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
901 entity->focalx = limbs[SHOPKEEPER][8][0]; // 0
902 entity->focaly = limbs[SHOPKEEPER][8][1]; // 0
903 entity->focalz = limbs[SHOPKEEPER][8][2]; // 4
904 entity->behavior = &actShopkeeperLimb;
905 entity->parent = my->getUID();
906 node = list_AddNodeLast(&my->children);
907 node->element = entity;
908 node->deconstructor = &emptyDeconstructor;
909 node->size = sizeof(Entity*);
910 my->bodyparts.push_back(entity);
911
912 // helmet
913 entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
914 entity->sizex = 4;
915 entity->sizey = 4;
916 entity->skill[2] = my->getUID();
917 entity->scalex = 1.01;
918 entity->scaley = 1.01;
919 entity->scalez = 1.01;
920 entity->flags[PASSABLE] = true;
921 entity->flags[NOUPDATE] = true;
922 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
923 entity->focalx = limbs[SHOPKEEPER][9][0]; // 0
924 entity->focaly = limbs[SHOPKEEPER][9][1]; // 0
925 entity->focalz = limbs[SHOPKEEPER][9][2]; // -1.75
926 entity->behavior = &actShopkeeperLimb;
927 entity->parent = my->getUID();
928 node = list_AddNodeLast(&my->children);
929 node->element = entity;
930 node->deconstructor = &emptyDeconstructor;
931 node->size = sizeof(Entity*);
932 my->bodyparts.push_back(entity);
933
934 // mask
935 entity = newEntity(-1, 0, map.entities, nullptr); //Limb entity.
936 entity->sizex = 4;
937 entity->sizey = 4;
938 entity->skill[2] = my->getUID();
939 entity->scalex = .99;
940 entity->scaley = .99;
941 entity->scalez = .99;
942 entity->flags[PASSABLE] = true;
943 entity->flags[NOUPDATE] = true;
944 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
945 entity->focalx = limbs[SHOPKEEPER][10][0]; // 0
946 entity->focaly = limbs[SHOPKEEPER][10][1]; // 0
947 entity->focalz = limbs[SHOPKEEPER][10][2]; // .5
948 entity->behavior = &actShopkeeperLimb;
949 entity->parent = my->getUID();
950 node = list_AddNodeLast(&my->children);
951 node->element = entity;
952 node->deconstructor = &emptyDeconstructor;
953 node->size = sizeof(Entity*);
954 my->bodyparts.push_back(entity);
955 }
956
actShopkeeperLimb(Entity * my)957 void actShopkeeperLimb(Entity* my)
958 {
959 my->actMonsterLimb();
960 }
961
shopkeeperDie(Entity * my)962 void shopkeeperDie(Entity* my)
963 {
964 int c;
965 for ( c = 0; c < 5; c++ )
966 {
967 Entity* gib = spawnGib(my);
968 serverSpawnGibForClient(gib);
969 }
970
971 my->spawnBlood();
972
973 my->removeMonsterDeathNodes();
974
975 list_RemoveNode(my->mynode);
976 return;
977 }
978
979 #define SHOPKEEPERWALKSPEED .15
980
shopkeeperMoveBodyparts(Entity * my,Stat * myStats,double dist)981 void shopkeeperMoveBodyparts(Entity* my, Stat* myStats, double dist)
982 {
983 node_t* node;
984 Entity* entity = nullptr, *entity2 = nullptr;
985 Entity* rightbody = nullptr;
986 Entity* weaponarm = nullptr;
987 int bodypart;
988 bool wearingring = false;
989
990 if ( multiplayer != CLIENT && myStats->MISC_FLAGS[STAT_FLAG_MYSTERIOUS_SHOPKEEP] > 0 )
991 {
992 if ( my->ticks == 5 * TICKS_PER_SECOND )
993 {
994 serverUpdateEntitySkill(my, 18); // update the store type for clients.
995 }
996 if ( myStats->HP < ((3 * myStats->MAXHP) / 4) )
997 {
998 playSoundEntity(my, 77, 64);
999 createParticleErupt(my, 593);
1000 serverSpawnMiscParticles(my, PARTICLE_EFFECT_ERUPT, 593);
1001 my->removeMonsterDeathNodes();
1002 list_RemoveNode(my->mynode);
1003 return;
1004 }
1005 }
1006
1007 // set invisibility //TODO: isInvisible()?
1008 if ( multiplayer != CLIENT )
1009 {
1010 if ( myStats->ring != nullptr )
1011 if ( myStats->ring->type == RING_INVISIBILITY )
1012 {
1013 wearingring = true;
1014 }
1015 if ( myStats->cloak != nullptr )
1016 if ( myStats->cloak->type == CLOAK_INVISIBILITY )
1017 {
1018 wearingring = true;
1019 }
1020 if ( myStats->EFFECTS[EFF_INVISIBLE] == true || wearingring == true )
1021 {
1022 my->flags[INVISIBLE] = true;
1023 my->flags[BLOCKSIGHT] = false;
1024 bodypart = 0;
1025 for ( node = my->children.first; node != nullptr; node = node->next )
1026 {
1027 if ( bodypart < 2 )
1028 {
1029 bodypart++;
1030 continue;
1031 }
1032 if ( bodypart >= 7 )
1033 {
1034 break;
1035 }
1036 entity = (Entity*)node->element;
1037 if ( !entity->flags[INVISIBLE] )
1038 {
1039 entity->flags[INVISIBLE] = true;
1040 serverUpdateEntityBodypart(my, bodypart);
1041 }
1042 bodypart++;
1043 }
1044 }
1045 else
1046 {
1047 my->flags[INVISIBLE] = false;
1048 my->flags[BLOCKSIGHT] = true;
1049 bodypart = 0;
1050 for ( node = my->children.first; node != nullptr; node = node->next )
1051 {
1052 if ( bodypart < 2 )
1053 {
1054 bodypart++;
1055 continue;
1056 }
1057 if ( bodypart >= 7 )
1058 {
1059 break;
1060 }
1061 entity = (Entity*)node->element;
1062 if ( entity->flags[INVISIBLE] )
1063 {
1064 entity->flags[INVISIBLE] = false;
1065 serverUpdateEntityBodypart(my, bodypart);
1066 serverUpdateEntityFlag(my, INVISIBLE);
1067 }
1068 bodypart++;
1069 }
1070 }
1071
1072 // sleeping
1073 if ( myStats->EFFECTS[EFF_ASLEEP] )
1074 {
1075 my->z = 1.5;
1076 my->pitch = PI / 4;
1077 }
1078 else
1079 {
1080 my->z = -1;
1081 my->pitch = 0;
1082 }
1083 }
1084
1085 Entity* helmet = nullptr;
1086
1087 //Move bodyparts
1088 for (bodypart = 0, node = my->children.first; node != NULL; node = node->next, bodypart++)
1089 {
1090 if ( bodypart < LIMB_HUMANOID_TORSO )
1091 {
1092 continue;
1093 }
1094 entity = (Entity*)node->element;
1095 entity->x = my->x;
1096 entity->y = my->y;
1097 entity->z = my->z;
1098 if ( MONSTER_ATTACK == MONSTER_POSE_MAGIC_WINDUP1 && bodypart == LIMB_HUMANOID_RIGHTARM )
1099 {
1100 // don't let the creatures's yaw move the casting arm
1101 }
1102 else
1103 {
1104 entity->yaw = my->yaw;
1105 }
1106 if ( bodypart == LIMB_HUMANOID_RIGHTLEG || bodypart == LIMB_HUMANOID_LEFTARM )
1107 {
1108 my->humanoidAnimateWalk(entity, node, bodypart, SHOPKEEPERWALKSPEED, dist, 0.4);
1109 }
1110 else if ( bodypart == LIMB_HUMANOID_LEFTLEG || bodypart == LIMB_HUMANOID_RIGHTARM || bodypart == LIMB_HUMANOID_CLOAK )
1111 {
1112 // left leg, right arm, cloak.
1113 if ( bodypart == LIMB_HUMANOID_RIGHTARM )
1114 {
1115 weaponarm = entity;
1116 if ( my->monsterAttack > 0 )
1117 {
1118 my->handleWeaponArmAttack(weaponarm);
1119 }
1120 }
1121 my->humanoidAnimateWalk(entity, node, bodypart, SHOPKEEPERWALKSPEED, dist, 0.4);
1122
1123 if ( bodypart == LIMB_HUMANOID_CLOAK )
1124 {
1125 entity->fskill[0] = entity->pitch;
1126 entity->roll = my->roll - fabs(entity->pitch) / 2;
1127 entity->pitch = 0;
1128 }
1129 }
1130 switch ( bodypart )
1131 {
1132 // torso
1133 case 2:
1134 if ( multiplayer != CLIENT )
1135 {
1136 if ( myStats->breastplate == nullptr )
1137 {
1138 entity->sprite = 218;
1139 }
1140 else
1141 {
1142 entity->sprite = itemModel(myStats->breastplate);
1143 }
1144 if ( multiplayer == SERVER )
1145 {
1146 // update sprites for clients
1147 if ( entity->skill[10] != entity->sprite )
1148 {
1149 entity->skill[10] = entity->sprite;
1150 serverUpdateEntityBodypart(my, bodypart);
1151 }
1152 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1153 {
1154 serverUpdateEntityBodypart(my, bodypart);
1155 }
1156 }
1157 }
1158 entity->x -= .25 * cos(my->yaw);
1159 entity->y -= .25 * sin(my->yaw);
1160 entity->z += 2.5;
1161 break;
1162 // right leg
1163 case LIMB_HUMANOID_RIGHTLEG:
1164 if ( multiplayer != CLIENT )
1165 {
1166 if ( myStats->shoes == nullptr )
1167 {
1168 entity->sprite = 222;
1169 }
1170 else
1171 {
1172 my->setBootSprite(entity, SPRITE_BOOT_RIGHT_OFFSET);
1173 }
1174 if ( multiplayer == SERVER )
1175 {
1176 // update sprites for clients
1177 if ( entity->skill[10] != entity->sprite )
1178 {
1179 entity->skill[10] = entity->sprite;
1180 serverUpdateEntityBodypart(my, bodypart);
1181 }
1182 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1183 {
1184 serverUpdateEntityBodypart(my, bodypart);
1185 }
1186 }
1187 }
1188 entity->x += 1 * cos(my->yaw + PI / 2) + .25 * cos(my->yaw);
1189 entity->y += 1 * sin(my->yaw + PI / 2) + .25 * sin(my->yaw);
1190 entity->z += 5;
1191 if ( my->z >= 1.4 && my->z <= 1.6 )
1192 {
1193 entity->yaw += PI / 8;
1194 entity->pitch = -PI / 2;
1195 }
1196 break;
1197 // left leg
1198 case LIMB_HUMANOID_LEFTLEG:
1199 if ( multiplayer != CLIENT )
1200 {
1201 if ( myStats->shoes == nullptr )
1202 {
1203 entity->sprite = 221;
1204 }
1205 else
1206 {
1207 my->setBootSprite(entity, SPRITE_BOOT_LEFT_OFFSET);
1208 }
1209 if ( multiplayer == SERVER )
1210 {
1211 // update sprites for clients
1212 if ( entity->skill[10] != entity->sprite )
1213 {
1214 entity->skill[10] = entity->sprite;
1215 serverUpdateEntityBodypart(my, bodypart);
1216 }
1217 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1218 {
1219 serverUpdateEntityBodypart(my, bodypart);
1220 }
1221 }
1222 }
1223 entity->x -= 1 * cos(my->yaw + PI / 2) - .25 * cos(my->yaw);
1224 entity->y -= 1 * sin(my->yaw + PI / 2) - .25 * sin(my->yaw);
1225 entity->z += 5;
1226 if ( my->z >= 1.4 && my->z <= 1.6 )
1227 {
1228 entity->yaw -= PI / 8;
1229 entity->pitch = -PI / 2;
1230 }
1231 break;
1232 // right arm
1233 case LIMB_HUMANOID_RIGHTARM:
1234 {
1235 node_t* weaponNode = list_Node(&my->children, LIMB_HUMANOID_WEAPON);
1236 if ( weaponNode )
1237 {
1238 Entity* weapon = (Entity*)weaponNode->element;
1239 if ( MONSTER_ARMBENDED || (weapon->flags[INVISIBLE] && my->monsterState != MONSTER_STATE_ATTACK) )
1240 {
1241 // if weapon invisible and I'm not attacking, relax arm.
1242 entity->focalx = limbs[SHOPKEEPER][4][0]; // 0
1243 entity->focaly = limbs[SHOPKEEPER][4][1]; // 0
1244 entity->focalz = limbs[SHOPKEEPER][4][2]; // 1.5
1245 entity->sprite = 220;
1246 }
1247 else
1248 {
1249 // else flex arm.
1250 entity->focalx = limbs[SHOPKEEPER][4][0] + 0.75;
1251 entity->focaly = limbs[SHOPKEEPER][4][1];
1252 entity->focalz = limbs[SHOPKEEPER][4][2] - 0.75;
1253 entity->sprite = 111;
1254 }
1255 }
1256 entity->x += 2.25 * cos(my->yaw + PI / 2) - .20 * cos(my->yaw);
1257 entity->y += 2.25 * sin(my->yaw + PI / 2) - .20 * sin(my->yaw);
1258 entity->z += 1.5;
1259 entity->yaw += MONSTER_WEAPONYAW;
1260 if ( my->z >= 1.4 && my->z <= 1.6 )
1261 {
1262 entity->pitch = 0;
1263 }
1264 break;
1265 }
1266 // left arm
1267 case LIMB_HUMANOID_LEFTARM:
1268 {
1269 node_t* shieldNode = list_Node(&my->children, 8);
1270 if ( shieldNode )
1271 {
1272 Entity* shield = (Entity*)shieldNode->element;
1273 if ( shield->flags[INVISIBLE] && (my->monsterState != MONSTER_STATE_ATTACK) )
1274 {
1275 // if shield invisible and I'm not attacking, relax arm.
1276 entity->focalx = limbs[SHOPKEEPER][5][0]; // 0
1277 entity->focaly = limbs[SHOPKEEPER][5][1]; // 0
1278 entity->focalz = limbs[SHOPKEEPER][5][2]; // 1.5
1279 entity->sprite = 219;
1280 }
1281 else
1282 {
1283 // else flex arm.
1284 entity->focalx = limbs[SHOPKEEPER][5][0] + 0.75;
1285 entity->focaly = limbs[SHOPKEEPER][5][1];
1286 entity->focalz = limbs[SHOPKEEPER][5][2] - 0.75;
1287 entity->sprite = 112;
1288 }
1289 }
1290 entity->x -= 2.25 * cos(my->yaw + PI / 2) + .20 * cos(my->yaw);
1291 entity->y -= 2.25 * sin(my->yaw + PI / 2) + .20 * sin(my->yaw);
1292 entity->z += 1.5;
1293 if ( my->z >= 1.4 && my->z <= 1.6 )
1294 {
1295 entity->pitch = 0;
1296 }
1297 break;
1298 }
1299 // weapon
1300 case LIMB_HUMANOID_WEAPON:
1301 if ( multiplayer != CLIENT )
1302 {
1303 if ( myStats->weapon == nullptr || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()?
1304 {
1305 entity->flags[INVISIBLE] = true;
1306 }
1307 else
1308 {
1309 entity->sprite = itemModel(myStats->weapon);
1310 if ( itemCategory(myStats->weapon) == SPELLBOOK )
1311 {
1312 entity->flags[INVISIBLE] = true;
1313 }
1314 else
1315 {
1316 entity->flags[INVISIBLE] = false;
1317 }
1318 }
1319 if ( multiplayer == SERVER )
1320 {
1321 // update sprites for clients
1322 if ( entity->skill[10] != entity->sprite )
1323 {
1324 entity->skill[10] = entity->sprite;
1325 serverUpdateEntityBodypart(my, bodypart);
1326 }
1327 if ( entity->skill[11] != entity->flags[INVISIBLE] )
1328 {
1329 entity->skill[11] = entity->flags[INVISIBLE];
1330 serverUpdateEntityBodypart(my, bodypart);
1331 }
1332 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1333 {
1334 serverUpdateEntityBodypart(my, bodypart);
1335 }
1336 }
1337 }
1338 else
1339 {
1340 if ( entity->sprite <= 0 )
1341 {
1342 entity->flags[INVISIBLE] = true;
1343 }
1344 }
1345 if ( weaponarm != nullptr )
1346 {
1347 my->handleHumanoidWeaponLimb(entity, weaponarm);
1348 }
1349 break;
1350 // shield
1351 case LIMB_HUMANOID_SHIELD:
1352 if ( multiplayer != CLIENT )
1353 {
1354 if ( myStats->shield == nullptr )
1355 {
1356 entity->flags[INVISIBLE] = true;
1357 entity->sprite = 0;
1358 }
1359 else
1360 {
1361 entity->flags[INVISIBLE] = false;
1362 entity->sprite = itemModel(myStats->shield);
1363 if ( itemTypeIsQuiver(myStats->shield->type) )
1364 {
1365 entity->handleQuiverThirdPersonModel(*myStats);
1366 }
1367 }
1368 if ( myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()?
1369 {
1370 entity->flags[INVISIBLE] = true;
1371 }
1372 if ( multiplayer == SERVER )
1373 {
1374 // update sprites for clients
1375 if ( entity->skill[10] != entity->sprite )
1376 {
1377 entity->skill[10] = entity->sprite;
1378 serverUpdateEntityBodypart(my, bodypart);
1379 }
1380 if ( entity->skill[11] != entity->flags[INVISIBLE] )
1381 {
1382 entity->skill[11] = entity->flags[INVISIBLE];
1383 serverUpdateEntityBodypart(my, bodypart);
1384 }
1385 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1386 {
1387 serverUpdateEntityBodypart(my, bodypart);
1388 }
1389 }
1390 }
1391 else
1392 {
1393 if ( entity->sprite <= 0 )
1394 {
1395 entity->flags[INVISIBLE] = true;
1396 }
1397 }
1398 entity->x -= 2.5 * cos(my->yaw + PI / 2) + .20 * cos(my->yaw);
1399 entity->y -= 2.5 * sin(my->yaw + PI / 2) + .20 * sin(my->yaw);
1400 entity->z += 2.5;
1401 if ( entity->sprite == items[TOOL_TORCH].index )
1402 {
1403 entity2 = spawnFlame(entity, SPRITE_FLAME);
1404 entity2->x += 2 * cos(my->yaw);
1405 entity2->y += 2 * sin(my->yaw);
1406 entity2->z -= 2;
1407 }
1408 else if ( entity->sprite == items[TOOL_CRYSTALSHARD].index )
1409 {
1410 entity2 = spawnFlame(entity, SPRITE_CRYSTALFLAME);
1411 entity2->x += 2 * cos(my->yaw);
1412 entity2->y += 2 * sin(my->yaw);
1413 entity2->z -= 2;
1414 }
1415 else if ( entity->sprite == items[TOOL_LANTERN].index )
1416 {
1417 entity->z += 2;
1418 entity2 = spawnFlame(entity, SPRITE_FLAME);
1419 entity2->x += 2 * cos(my->yaw);
1420 entity2->y += 2 * sin(my->yaw);
1421 entity2->z += 1;
1422 }
1423 break;
1424 // cloak
1425 case LIMB_HUMANOID_CLOAK:
1426 if ( multiplayer != CLIENT )
1427 {
1428 if ( myStats->cloak == nullptr || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()?
1429 {
1430 entity->flags[INVISIBLE] = true;
1431 }
1432 else
1433 {
1434 entity->flags[INVISIBLE] = false;
1435 entity->sprite = itemModel(myStats->cloak);
1436 }
1437 if ( multiplayer == SERVER )
1438 {
1439 // update sprites for clients
1440 if ( entity->skill[10] != entity->sprite )
1441 {
1442 entity->skill[10] = entity->sprite;
1443 serverUpdateEntityBodypart(my, bodypart);
1444 }
1445 if ( entity->skill[11] != entity->flags[INVISIBLE] )
1446 {
1447 entity->skill[11] = entity->flags[INVISIBLE];
1448 serverUpdateEntityBodypart(my, bodypart);
1449 }
1450 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1451 {
1452 serverUpdateEntityBodypart(my, bodypart);
1453 }
1454 }
1455 }
1456 else
1457 {
1458 if ( entity->sprite <= 0 )
1459 {
1460 entity->flags[INVISIBLE] = true;
1461 }
1462 }
1463 entity->x -= cos(my->yaw);
1464 entity->y -= sin(my->yaw);
1465 entity->yaw += PI / 2;
1466 break;
1467 // helm
1468 case LIMB_HUMANOID_HELMET:
1469 helmet = entity;
1470 entity->focalx = limbs[SHOPKEEPER][9][0]; // 0
1471 entity->focaly = limbs[SHOPKEEPER][9][1]; // 0
1472 entity->focalz = limbs[SHOPKEEPER][9][2]; // -1.75
1473 entity->pitch = my->pitch;
1474 entity->roll = 0;
1475 if ( multiplayer != CLIENT )
1476 {
1477 entity->sprite = itemModel(myStats->helmet);
1478 if ( myStats->helmet == nullptr || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()?
1479 {
1480 entity->flags[INVISIBLE] = true;
1481 }
1482 else
1483 {
1484 entity->flags[INVISIBLE] = false;
1485 }
1486 if ( multiplayer == SERVER )
1487 {
1488 // update sprites for clients
1489 if ( entity->skill[10] != entity->sprite )
1490 {
1491 entity->skill[10] = entity->sprite;
1492 serverUpdateEntityBodypart(my, bodypart);
1493 }
1494 if ( entity->skill[11] != entity->flags[INVISIBLE] )
1495 {
1496 entity->skill[11] = entity->flags[INVISIBLE];
1497 serverUpdateEntityBodypart(my, bodypart);
1498 }
1499 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1500 {
1501 serverUpdateEntityBodypart(my, bodypart);
1502 }
1503 }
1504 }
1505 else
1506 {
1507 if ( entity->sprite <= 0 )
1508 {
1509 entity->flags[INVISIBLE] = true;
1510 }
1511 }
1512 my->setHelmetLimbOffset(entity);
1513 break;
1514 // mask
1515 case LIMB_HUMANOID_MASK:
1516 entity->focalx = limbs[SHOPKEEPER][10][0]; // 0
1517 entity->focaly = limbs[SHOPKEEPER][10][1]; // 0
1518 entity->focalz = limbs[SHOPKEEPER][10][2]; // .5
1519 entity->pitch = my->pitch;
1520 entity->roll = PI / 2;
1521 if ( multiplayer != CLIENT )
1522 {
1523 bool hasSteelHelm = false;
1524 if ( myStats->helmet )
1525 {
1526 if ( myStats->helmet->type == STEEL_HELM
1527 || myStats->helmet->type == CRYSTAL_HELM
1528 || myStats->helmet->type == ARTIFACT_HELM )
1529 {
1530 hasSteelHelm = true;
1531 }
1532 }
1533 if ( myStats->mask == nullptr || myStats->EFFECTS[EFF_INVISIBLE] || wearingring || hasSteelHelm ) //TODO: isInvisible()?
1534 {
1535 entity->flags[INVISIBLE] = true;
1536 }
1537 else
1538 {
1539 entity->flags[INVISIBLE] = false;
1540 }
1541 if ( myStats->mask != nullptr )
1542 {
1543 if ( myStats->mask->type == TOOL_GLASSES )
1544 {
1545 entity->sprite = 165; // GlassesWorn.vox
1546 }
1547 else
1548 {
1549 entity->sprite = itemModel(myStats->mask);
1550 }
1551 }
1552 if ( multiplayer == SERVER )
1553 {
1554 // update sprites for clients
1555 if ( entity->skill[10] != entity->sprite )
1556 {
1557 entity->skill[10] = entity->sprite;
1558 serverUpdateEntityBodypart(my, bodypart);
1559 }
1560 if ( entity->skill[11] != entity->flags[INVISIBLE] )
1561 {
1562 entity->skill[11] = entity->flags[INVISIBLE];
1563 serverUpdateEntityBodypart(my, bodypart);
1564 }
1565 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1566 {
1567 serverUpdateEntityBodypart(my, bodypart);
1568 }
1569 }
1570 }
1571 else
1572 {
1573 if ( entity->sprite <= 0 )
1574 {
1575 entity->flags[INVISIBLE] = true;
1576 }
1577 }
1578
1579 if ( entity->sprite != 165 )
1580 {
1581 if ( entity->sprite == items[MASK_SHAMAN].index )
1582 {
1583 entity->roll = 0;
1584 my->setHelmetLimbOffset(entity);
1585 my->setHelmetLimbOffsetWithMask(helmet, entity);
1586 }
1587 else
1588 {
1589 entity->focalx = limbs[SHOPKEEPER][10][0] + .35; // .35
1590 entity->focaly = limbs[SHOPKEEPER][10][1] - 2; // -2
1591 entity->focalz = limbs[SHOPKEEPER][10][2]; // .5
1592 }
1593 }
1594 else
1595 {
1596 entity->focalx = limbs[SHOPKEEPER][10][0] + .25; // .25
1597 entity->focaly = limbs[SHOPKEEPER][10][1] - 2.25; // -2.25
1598 entity->focalz = limbs[SHOPKEEPER][10][2]; // .5
1599 }
1600 break;
1601 }
1602 }
1603 // rotate shield a bit
1604 node_t* shieldNode = list_Node(&my->children, LIMB_HUMANOID_SHIELD);
1605 if ( shieldNode )
1606 {
1607 Entity* shieldEntity = (Entity*)shieldNode->element;
1608 if ( shieldEntity->sprite != items[TOOL_TORCH].index && shieldEntity->sprite != items[TOOL_LANTERN].index && shieldEntity->sprite != items[TOOL_CRYSTALSHARD].index )
1609 {
1610 shieldEntity->yaw -= PI / 6;
1611 }
1612 }
1613 if ( MONSTER_ATTACK > 0 && MONSTER_ATTACK <= MONSTER_POSE_MAGIC_CAST3 )
1614 {
1615 MONSTER_ATTACKTIME++;
1616 }
1617 else if ( MONSTER_ATTACK == 0 )
1618 {
1619 MONSTER_ATTACKTIME = 0;
1620 }
1621 else
1622 {
1623 // do nothing, don't reset attacktime or increment it.
1624 }
1625 }
1626
shopkeeperMysteriousGenerateInventory(ItemType orb)1627 void shopkeeperMysteriousGenerateInventory(ItemType orb)
1628 {
1629 /*std::vector<int> items;
1630 if ( orb == ARTIFACT_ORB_GREEN )
1631 {
1632 items.push_back(QUIVER_CRYSTAL);
1633 items.push_back(QUIVER_LIGHTWEIGHT);
1634 items.push_back(QUIVER_HUNTING);
1635 items.push_back(HEAVY_CROSSBOW);
1636 items.push_back(BOOMERANG);
1637 items.push_back(ARTIFACT_BOW);
1638 }
1639 else if ( orb == ARTIFACT_ORB_BLUE )
1640 {
1641 items.push_back(ARTIFACT_MACE);
1642 items.push_back(ENCHANTED_FEATHER);
1643 }
1644 else if ( orb == ARTIFACT_ORB_RED )
1645 {
1646 items.push_back(ARTIFACT_AXE);
1647 items.push_back(ARTIFACT_SWORD);
1648 items.push_back(ARTIFACT_SPEAR);
1649 }
1650
1651 if ( !items.empty() )
1652 {
1653 if ( shopkeeperMysteriousItems.find(orb) == shopkeeperMysteriousItems.end() )
1654 {
1655 shopkeeperMysteriousItems.insert(std::make_pair(orb, items));
1656 }
1657 else
1658 {
1659 shopkeeperMysteriousItems[orb] = items;
1660 }
1661 }*/
1662 }