1 /*-------------------------------------------------------------------------------
2
3 BARONY
4 File: actfountain.cpp
5 Desc: behavior function for fountains
6
7 Copyright 2013-2016 (c) Turning Wheel LLC, all rights reserved.
8 See LICENSE for details.
9
10 -------------------------------------------------------------------------------*/
11
12 #include <utility>
13 #include "main.hpp"
14 #include "game.hpp"
15 #include "stat.hpp"
16 #include "entity.hpp"
17 #include "monster.hpp"
18 #include "sound.hpp"
19 #include "items.hpp"
20 #include "net.hpp"
21 #include "collision.hpp"
22 #include "player.hpp"
23 #include "colors.hpp"
24 #include "scores.hpp"
25
26 //Fountain functions.
27 const std::vector<int> fountainPotionDropChances =
28 {
29 5, //POTION_WATER,
30 20, //POTION_BOOZE,
31 10, //POTION_JUICE,
32 10, //POTION_SICKNESS,
33 5, //POTION_CONFUSION,
34 2, //POTION_EXTRAHEALING,
35 5, //POTION_HEALING,
36 5, //POTION_CUREAILMENT,
37 10, //POTION_BLINDNESS,
38 5, //POTION_RESTOREMAGIC,
39 2, //POTION_INVISIBILITY,
40 2, //POTION_LEVITATION,
41 5, //POTION_SPEED,
42 10, //POTION_ACID,
43 2, //POTION_PARALYSIS,
44 2 //POTION_POLYMORPH
45 };
46
47 const std::vector<std::pair<int, int>> potionStandardAppearanceMap =
48 {
49 // second element is appearance.
50 { POTION_WATER, 0 },
51 { POTION_BOOZE, 2 },
52 { POTION_JUICE, 3 },
53 { POTION_SICKNESS, 1 },
54 { POTION_CONFUSION, 0 },
55 { POTION_EXTRAHEALING, 0 },
56 { POTION_HEALING, 0 },
57 { POTION_CUREAILMENT, 0 },
58 { POTION_BLINDNESS, 0 },
59 { POTION_RESTOREMAGIC, 1 },
60 { POTION_INVISIBILITY, 0 },
61 { POTION_LEVITATION, 0 },
62 { POTION_SPEED, 0 },
63 { POTION_ACID, 0 },
64 { POTION_PARALYSIS, 1 },
65 { POTION_POLYMORPH, 0 },
66 { POTION_FIRESTORM, 0 },
67 { POTION_ICESTORM, 0 },
68 { POTION_THUNDERSTORM, 0 },
69 { POTION_STRENGTH, 0 }
70 };
71
72 std::mt19937 fountainSeed(rand());
73 std::discrete_distribution<> fountainDistribution(fountainPotionDropChances.begin(), fountainPotionDropChances.end());
74
fountainGeneratePotionDrop()75 std::pair<int, int> fountainGeneratePotionDrop()
76 {
77 auto keyPair = potionStandardAppearanceMap.at(fountainDistribution(fountainSeed));
78 return std::make_pair(keyPair.first, keyPair.second);
79 }
80
81 /*-------------------------------------------------------------------------------
82
83 act*
84
85 The following function describes an entity behavior. The function
86 takes a pointer to the entity that uses it as an argument.
87
88 my->skill[0] is either 0 or 1. If it is 0, the fountain is dry and cannot be used
89 my->skill[1] is either 0, 1, 2, or 3. It is set at the creation of the fountain.
90 Those values correspond to what the fountain does:
91 0 = spawn succubus, 1 = raise hunger, 2 = random potion effect, 3 = bless equipment
92 my->skill[3] is a random potion effect. It is set at the creation of the fountain
93
94 -------------------------------------------------------------------------------*/
95
actFountain(Entity * my)96 void actFountain(Entity* my)
97 {
98 Entity* entity;
99
100 //messagePlayer(0, "actFountain()");
101 //TODO: Temporary mechanism testing code.
102 /*
103 if( multiplayer != CLIENT ) {
104 if (my->skill[28]) {
105 //All it does is change its sprite to sink if it's powered.
106 if (my->skill[28] == 1) {
107 my->sprite = 163;
108 } else {
109 my->sprite = 164;
110 }
111 }
112 }*/
113 //****************END TEST CODE***************
114
115 //TODO: Sounds.
116
117 // spray water
118 if ( my->skill[0] > 0 )
119 {
120 #define FOUNTAIN_AMBIENCE my->skill[7]
121 FOUNTAIN_AMBIENCE--;
122 if ( FOUNTAIN_AMBIENCE <= 0 )
123 {
124 FOUNTAIN_AMBIENCE = TICKS_PER_SECOND * 6;
125 playSoundEntityLocal(my, 135, 32 );
126 }
127 entity = spawnGib(my);
128 entity->flags[INVISIBLE] = false;
129 entity->y -= 2;
130 entity->z -= 8;
131 entity->flags[SPRITE] = false;
132 entity->flags[NOUPDATE] = true;
133 entity->flags[UPDATENEEDED] = false;
134 entity->skill[4] = 7;
135 entity->sprite = 4;
136 entity->yaw = (rand() % 360) * PI / 180.0;
137 entity->pitch = (rand() % 360) * PI / 180.0;
138 entity->roll = (rand() % 360) * PI / 180.0;
139 entity->vel_x = 0;
140 entity->vel_y = 0;
141 entity->vel_z = .25;
142 entity->fskill[3] = 0.03;
143 }
144
145 // the rest of the function is server-side.
146 if ( multiplayer == CLIENT )
147 {
148 return;
149 }
150
151 //Using the fountain (TODO: Monsters using it?).
152 int i;
153 for (i = 0; i < MAXPLAYERS; ++i)
154 {
155 if ( (i == 0 && selectedEntity == my) || (client_selected[i] == my) )
156 {
157 if (inrange[i]) //Act on it only if the player (or monster, if/when this is changed to support monster interaction?) is in range.
158 {
159 //First check that it's not depleted.
160 if (my->skill[0] == 0)
161 {
162 //Depleted
163 messagePlayer(i, language[467]);
164 }
165 else
166 {
167 if (players[i]->entity->flags[BURNING])
168 {
169 messagePlayer(i, language[468]);
170 players[i]->entity->flags[BURNING] = false;
171 serverUpdateEntityFlag(players[i]->entity, BURNING);
172 steamAchievementClient(i, "BARONY_ACH_HOT_SHOWER");
173 }
174 int potionDropQuantity = 0;
175 if ( stats[i] && (stats[i]->type == GOATMAN || stats[i]->playerRace == RACE_GOATMAN) && stats[i]->appearance == 0 )
176 {
177 // drop some random potions.
178 switch ( rand() % 10 )
179 {
180 case 0:
181 case 1:
182 case 2:
183 case 3:
184 potionDropQuantity = 1;
185 break;
186 case 4:
187 case 5:
188 potionDropQuantity = 2;
189 break;
190 case 6:
191 potionDropQuantity = 3;
192 break;
193 case 7:
194 case 8:
195 case 9:
196 // nothing
197 potionDropQuantity = 0;
198 break;
199 default:
200 break;
201 }
202
203 if ( potionDropQuantity > 0 )
204 {
205 steamStatisticUpdateClient(i, STEAM_STAT_BOTTLE_NOSED, STEAM_STAT_INT, 1);
206 }
207
208 for ( int j = 0; j < potionDropQuantity; ++j )
209 {
210 std::pair<int, int> generatedPotion = fountainGeneratePotionDrop();
211 ItemType type = static_cast<ItemType>(generatedPotion.first);
212 int appearance = generatedPotion.second;
213 Item* item = newItem(type, EXCELLENT, 0, 1, appearance, false, NULL);
214 Entity* dropped = dropItemMonster(item, my, NULL);
215 dropped->yaw = ((0 + rand() % 360) / 180.f) * PI;
216 dropped->vel_x = (0.75 + .025 * (rand() % 11)) * cos(dropped->yaw);
217 dropped->vel_y = (0.75 + .025 * (rand() % 11)) * sin(dropped->yaw);
218 dropped->vel_z = (-10 - rand() % 20) * .01;
219 dropped->flags[USERFLAG1] = false;
220 }
221 }
222 switch (my->skill[1])
223 {
224 case 0:
225 {
226 playSoundEntity(players[i]->entity, 52, 64);
227 //Spawn succubus.
228 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 128, 0);
229 Entity* spawnedMonster = nullptr;
230
231 if ( !strncmp(map.name, "Underworld", 10) )
232 {
233 Monster creature = SUCCUBUS;
234 if ( rand() % 2 )
235 {
236 creature = INCUBUS;
237 }
238 for ( int c = 0; spawnedMonster == nullptr && c < 5; ++c )
239 {
240 switch ( c )
241 {
242 case 0:
243 spawnedMonster = summonMonster(creature, my->x, my->y);
244 break;
245 case 1:
246 spawnedMonster = summonMonster(creature, my->x + 16, my->y);
247 break;
248 case 2:
249 spawnedMonster = summonMonster(creature, my->x - 16, my->y);
250 break;
251 case 3:
252 spawnedMonster = summonMonster(creature, my->x, my->y + 16);
253 break;
254 case 4:
255 spawnedMonster = summonMonster(creature, my->x, my->y - 16);
256 break;
257 }
258 }
259 if ( spawnedMonster )
260 {
261 if ( creature == INCUBUS )
262 {
263 messagePlayerColor(i, color, language[2519]);
264 Stat* tmpStats = spawnedMonster->getStats();
265 if ( tmpStats )
266 {
267 strcpy(tmpStats->name, "lesser incubus");
268 }
269 }
270 else
271 {
272 messagePlayerColor(i, color, language[469]);
273 }
274 }
275 }
276 else if ( currentlevel < 10 )
277 {
278 messagePlayerColor(i, color, language[469]);
279 spawnedMonster = summonMonster(SUCCUBUS, my->x, my->y);
280 }
281 else if ( currentlevel < 20 )
282 {
283 if ( rand() % 2 )
284 {
285 spawnedMonster = summonMonster(INCUBUS, my->x, my->y);
286 Stat* tmpStats = spawnedMonster->getStats();
287 if ( tmpStats )
288 {
289 strcpy(tmpStats->name, "lesser incubus");
290 }
291 messagePlayerColor(i, color, language[2519]);
292 }
293 else
294 {
295 messagePlayerColor(i, color, language[469]);
296 spawnedMonster = summonMonster(SUCCUBUS, my->x, my->y);
297 }
298 }
299 else
300 {
301 messagePlayerColor(i, color, language[2519]);
302 spawnedMonster = summonMonster(INCUBUS, my->x, my->y);
303 }
304 break;
305 }
306 case 1:
307 if ( stats[i]->type != VAMPIRE )
308 {
309 messagePlayer(i, language[470]);
310 messagePlayer(i, language[471]);
311 playSoundEntity(players[i]->entity, 52, 64);
312 stats[i]->HUNGER += 100;
313 players[i]->entity->modHP(5);
314 }
315 else
316 {
317 players[i]->entity->modHP(-3);
318 playSoundEntity(players[i]->entity, 28, 64);
319 playSoundEntity(players[i]->entity, 249, 128);
320 players[i]->entity->setObituary(language[1533]);
321
322 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
323 messagePlayerColor(i, color, language[3183]);
324 if ( i == 0 || splitscreen )
325 {
326 cameravars[i].shakex += .1;
327 cameravars[i].shakey += 10;
328 }
329 else if ( multiplayer == SERVER && i > 0 )
330 {
331 strcpy((char*)net_packet->data, "SHAK");
332 net_packet->data[4] = 10; // turns into .1
333 net_packet->data[5] = 10;
334 net_packet->address.host = net_clients[i - 1].host;
335 net_packet->address.port = net_clients[i - 1].port;
336 net_packet->len = 6;
337 sendPacketSafe(net_sock, -1, net_packet, i - 1);
338 }
339 }
340 break;
341 case 2:
342 {
343 //Potion effect. Potion effect is stored in my->skill[3], randomly chosen when the fountain is created.
344 messagePlayer(i, language[470]);
345 Item* item = newItem(static_cast<ItemType>(POTION_WATER + my->skill[3]), static_cast<Status>(4), 0, 1, 0, false, NULL);
346 useItem(item, i, my);
347 // Long live the mystical fountain of TODO.
348 break;
349 }
350 case 3:
351 {
352 // bless all equipment
353 playSoundEntity(players[i]->entity, 52, 64);
354 //playSoundEntity(players[i]->entity, 167, 64);
355 Uint32 textcolor = SDL_MapRGB(mainsurface->format, 0, 255, 255);
356 messagePlayerColor(i, textcolor, language[471]);
357 messagePlayerColor(i, textcolor, language[473]);
358 bool stuckOnYouSuccess = false;
359 if ( stats[i]->helmet )
360 {
361 if ( stats[i]->type == SUCCUBUS && stats[i]->helmet->beatitude == 0 )
362 {
363 stuckOnYouSuccess = true;
364 }
365 stats[i]->helmet->beatitude++;
366 }
367 if ( stats[i]->breastplate )
368 {
369 if ( stats[i]->type == SUCCUBUS && stats[i]->breastplate->beatitude == 0 )
370 {
371 stuckOnYouSuccess = true;
372 }
373 stats[i]->breastplate->beatitude++;
374 }
375 if ( stats[i]->gloves )
376 {
377 if ( stats[i]->type == SUCCUBUS && stats[i]->gloves->beatitude == 0 )
378 {
379 stuckOnYouSuccess = true;
380 }
381 stats[i]->gloves->beatitude++;
382 }
383 if ( stats[i]->shoes )
384 {
385 if ( stats[i]->type == SUCCUBUS && stats[i]->shoes->beatitude == 0 )
386 {
387 stuckOnYouSuccess = true;
388 }
389 stats[i]->shoes->beatitude++;
390 }
391 if ( stats[i]->shield )
392 {
393 if ( stats[i]->type == SUCCUBUS && stats[i]->shield->beatitude == 0 )
394 {
395 stuckOnYouSuccess = true;
396 }
397 stats[i]->shield->beatitude++;
398 }
399 if ( stats[i]->weapon )
400 {
401 if ( stats[i]->type == SUCCUBUS && stats[i]->weapon->beatitude == 0 )
402 {
403 stuckOnYouSuccess = true;
404 }
405 stats[i]->weapon->beatitude++;
406 }
407 if ( stats[i]->cloak )
408 {
409 if ( stats[i]->type == SUCCUBUS && stats[i]->cloak->beatitude == 0 )
410 {
411 stuckOnYouSuccess = true;
412 }
413 stats[i]->cloak->beatitude++;
414 }
415 if ( stats[i]->amulet )
416 {
417 if ( stats[i]->type == SUCCUBUS && stats[i]->amulet->beatitude == 0 )
418 {
419 stuckOnYouSuccess = true;
420 }
421 stats[i]->amulet->beatitude++;
422 }
423 if ( stats[i]->ring )
424 {
425 if ( stats[i]->type == SUCCUBUS && stats[i]->ring->beatitude == 0 )
426 {
427 stuckOnYouSuccess = true;
428 }
429 stats[i]->ring->beatitude++;
430 }
431 if ( stats[i]->mask )
432 {
433 if ( stats[i]->type == SUCCUBUS && stats[i]->mask->beatitude == 0 )
434 {
435 stuckOnYouSuccess = true;
436 }
437 stats[i]->mask->beatitude++;
438 }
439 if ( multiplayer == SERVER && i > 0 )
440 {
441 strcpy((char*)net_packet->data, "BLES");
442 net_packet->address.host = net_clients[i - 1].host;
443 net_packet->address.port = net_clients[i - 1].port;
444 net_packet->len = 4;
445 sendPacketSafe(net_sock, -1, net_packet, i - 1);
446 }
447 if ( stuckOnYouSuccess )
448 {
449 steamAchievementClient(i, "BARONY_ACH_STUCK_ON_YOU");
450 }
451 break;
452 }
453 case 4:
454 {
455 // bless one piece of equipment
456 playSoundEntity(players[i]->entity, 52, 64);
457 //playSoundEntity(players[i]->entity, 167, 64);
458 Uint32 textcolor = SDL_MapRGB(mainsurface->format, 0, 255, 255);
459 messagePlayerColor(i, textcolor, language[471]);
460 //Choose only one piece of equipment to bless.
461
462 //First, Figure out what equipment is available.
463 std::vector<std::pair<Item*, Uint32>> items;
464 if ( stats[i]->helmet )
465 {
466 items.push_back(std::pair<Item*,int>(stats[i]->helmet, 0));
467 }
468 if ( stats[i]->breastplate )
469 {
470 items.push_back(std::pair<Item*,int>(stats[i]->breastplate, 1));
471 }
472 if ( stats[i]->gloves )
473 {
474 items.push_back(std::pair<Item*,int>(stats[i]->gloves, 2));
475 }
476 if ( stats[i]->shoes )
477 {
478 items.push_back(std::pair<Item*,int>(stats[i]->shoes, 3));
479 }
480 if ( stats[i]->shield )
481 {
482 items.push_back(std::pair<Item*,int>(stats[i]->shield, 4));
483 }
484 if ( stats[i]->weapon && stats[i]->weapon->type != POTION_EMPTY )
485 {
486 items.push_back(std::pair<Item*,int>(stats[i]->weapon, 5));
487 }
488 if ( stats[i]->cloak )
489 {
490 items.push_back(std::pair<Item*,int>(stats[i]->cloak, 6));
491 }
492 if ( stats[i]->amulet )
493 {
494 items.push_back(std::pair<Item*,int>(stats[i]->amulet, 7));
495 }
496 if ( stats[i]->ring )
497 {
498 items.push_back(std::pair<Item*,int>(stats[i]->ring, 8));
499 }
500 if ( stats[i]->mask )
501 {
502 items.push_back(std::pair<Item*,int>(stats[i]->mask, 9));
503 }
504
505 if ( items.size() )
506 {
507 messagePlayerColor(i, textcolor, language[2592]); //"The fountain blesses a piece of equipment"
508 //Randomly choose a piece of equipment.
509 std::pair<Item*, Uint32> chosen = items[rand()%items.size()];
510 if ( chosen.first->beatitude == 0 )
511 {
512 if ( stats[i]->type == SUCCUBUS )
513 {
514 steamAchievementClient(i, "BARONY_ACH_STUCK_ON_YOU");
515 }
516 }
517 chosen.first->beatitude++;
518
519 if ( multiplayer == SERVER && i > 0 )
520 {
521 strcpy((char*)net_packet->data, "BLE1");
522 SDLNet_Write32(chosen.second, &net_packet->data[4]);
523 net_packet->address.host = net_clients[i - 1].host;
524 net_packet->address.port = net_clients[i - 1].port;
525 net_packet->len = 8;
526 sendPacketSafe(net_sock, -1, net_packet, i - 1);
527 }
528 }
529 //Does nothing if no valid items.
530 break;
531 }
532 default:
533 break;
534 }
535 if ( potionDropQuantity > 0 )
536 {
537 playSoundEntity(my, 47 + rand() % 3, 64);
538 }
539 if ( potionDropQuantity > 1 )
540 {
541 messagePlayerColor(i, uint32ColorGreen(*mainsurface), language[3245], potionDropQuantity);
542 }
543 else if ( potionDropQuantity == 1 )
544 {
545 messagePlayerColor(i, uint32ColorGreen(*mainsurface), language[3246]);
546 }
547 messagePlayer(i, language[474]);
548 my->skill[0] = 0; //Dry up fountain.
549 serverUpdateEntitySkill(my, 0);
550 //TODO: messagePlayersInSight() instead.
551 }
552 //Then perform the effect randomly determined when the fountain was created.
553 return;
554 }
555 }
556 }
557 }
558