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