moved almost all hardcoded constants to "define.dat"
[k8-i-v-a-n.git] / src / game / game.cpp
blobc7db0709321de4e193d76eca1bf6d07bf8547589
1 /*
3 * Iter Vehemens ad Necem (IVAN)
4 * Copyright (C) Timo Kiviluoto
5 * Released under the GNU General
6 * Public License
8 * See LICENSING which should be included
9 * along with this file for more details
13 #include <algorithm>
14 #include <cstdarg>
16 #include <sys/stat.h>
17 #include <sys/types.h>
18 #include "whandler.h"
19 #include "hscore.h"
20 #include "rawbit.h"
21 #include "message.h"
22 #include "feio.h"
23 #include "team.h"
24 #include "iconf.h"
25 #include "allocate.h"
26 #include "pool.h"
27 #include "god.h"
28 #include "proto.h"
29 #include "stack.h"
30 #include "felist.h"
31 #include "human.h"
32 #include "nonhuman.h"
33 #include "wsquare.h"
34 #include "game.h"
35 #include "graphics.h"
36 #include "bitmap.h"
37 #include "fesave.h"
38 #include "feparse.h"
39 #include "miscitem.h"
40 #include "room.h"
41 #include "materias.h"
42 #include "rain.h"
43 #include "gear.h"
44 #include "fetime.h"
45 #include "balance.h"
46 #include "confdef.h"
47 #include "wmapset.h"
49 #define SAVE_FILE_VERSION 134 // Increment this if changes make savefiles incompatible
50 #define BONE_FILE_VERSION 119 // Increment this if changes make bonefiles incompatible
52 #define LOADED 0
53 #define NEW_GAME 1
54 #define BACK 2
57 std::stack<TextInput *> game::mFEStack;
58 character *game::mChar = 0;
59 ccharacter *game::mActor = 0;
60 ccharacter *game::mSecondActor = 0;
61 item *game::mItem = 0;
62 int game::mResult = 0;
65 int game::CurrentLevelIndex;
66 truth game::InWilderness = false;
67 worldmap* game::WorldMap;
68 area* game::AreaInLoad;
69 square* game::SquareInLoad;
70 dungeon** game::Dungeon;
71 int game::CurrentDungeonIndex;
72 feuLong game::NextCharacterID = 1;
73 feuLong game::NextItemID = 1;
74 feuLong game::NextTrapID = 1;
75 team** game::Team;
76 feuLong game::LOSTick;
77 v2 game::CursorPos(-1, -1);
78 truth game::Zoom;
79 truth game::Generating = false;
80 double game::AveragePlayerArmStrengthExperience;
81 double game::AveragePlayerLegStrengthExperience;
82 double game::AveragePlayerDexterityExperience;
83 double game::AveragePlayerAgilityExperience;
84 int game::Teams;
85 int game::Dungeons;
86 int game::StoryState;
87 /* */
88 int game::XinrochTombStoryState;
89 int game::MondedrPass;
90 int game::RingOfThieves;
91 int game::Masamune;
92 int game::Muramasa;
93 int game::LoricatusHammer;
94 int game::Liberator;
95 int game::OmmelBloodMission;
96 int game::RegiiTalkState;
97 /* */
98 massacremap game::PlayerMassacreMap;
99 massacremap game::PetMassacreMap;
100 massacremap game::MiscMassacreMap;
101 sLong game::PlayerMassacreAmount = 0;
102 sLong game::PetMassacreAmount = 0;
103 sLong game::MiscMassacreAmount = 0;
104 boneidmap game::BoneItemIDMap;
105 boneidmap game::BoneCharacterIDMap;
106 truth game::TooGreatDangerFoundTruth;
107 itemvectorvector game::ItemDrawVector;
108 charactervector game::CharacterDrawVector;
109 truth game::SumoWrestling;
110 liquid* game::GlobalRainLiquid;
111 v2 game::GlobalRainSpeed;
112 sLong game::GlobalRainTimeModifier;
113 truth game::PlayerSumoChampion;
114 truth game::PlayerSolicitusChampion;
115 feuLong game::SquarePartEmitationTick = 0;
116 sLong game::Turn;
117 truth game::PlayerRunning;
118 character* game::LastPetUnderCursor;
119 charactervector game::PetVector;
120 double game::DangerFound;
121 int game::OldAttribute[ATTRIBUTES];
122 int game::NewAttribute[ATTRIBUTES];
123 int game::LastAttributeChangeTick[ATTRIBUTES];
124 int game::NecroCounter;
125 int game::CursorData;
126 truth game::CausePanicFlag;
128 truth game::Loading = false;
129 truth game::JumpToPlayerBe = false;
130 truth game::InGetCommand = false;
131 character *game::Petrus = 0;
132 time_t game::TimePlayedBeforeLastLoad;
133 time_t game::LastLoad;
134 time_t game::GameBegan;
135 truth game::PlayerHasReceivedAllGodsKnownBonus;
137 festring game::AutoSaveFileName = game::GetSavePath()+"AutoSave";
138 cchar *const game::Alignment[] = { "L++", "L+", "L", "L-", "N+", "N=", "N-", "C+", "C", "C-", "C--" };
139 god **game::God;
141 cint game::MoveNormalCommandKey[] = { KEY_HOME, KEY_UP, KEY_PAGE_UP, KEY_LEFT, KEY_RIGHT, KEY_END, KEY_DOWN, KEY_PAGE_DOWN, '.' };
142 int game::MoveAbnormalCommandKey[] = { '7','8','9','u','o','j','k','l','.' };
144 cv2 game::MoveVector[] = { v2(-1, -1), v2(0, -1), v2(1, -1), v2(-1, 0), v2(1, 0), v2(-1, 1), v2(0, 1), v2(1, 1), v2(0, 0) };
145 cv2 game::RelativeMoveVector[] = { v2(-1, -1), v2(1, 0), v2(1, 0), v2(-2, 1), v2(2, 0), v2(-2, 1), v2(1, 0), v2(1, 0), v2(-1, -1) };
146 cv2 game::BasicMoveVector[] = { v2(-1, 0), v2(1, 0), v2(0, -1), v2(0, 1) };
147 cv2 game::LargeMoveVector[] = { v2(-1, -1), v2(0, -1), v2(1, -1), v2(2, -1), v2(-1, 0), v2(2, 0), v2(-1, 1), v2(2, 1), v2(-1, 2), v2(0, 2), v2(1, 2), v2(2, 2), v2(0, 0), v2(1, 0), v2(0, 1), v2(1, 1) };
148 cint game::LargeMoveDirection[] = { 0, 1, 1, 2, 3, 4, 3, 4, 5, 6, 6, 7, 8, 8, 8, 8 };
150 truth game::LOSUpdateRequested = false;
151 uChar ***game::LuxTable = 0;
152 truth game::Running;
153 character *game::Player;
154 v2 game::Camera(0, 0);
155 feuLong game::Tick;
156 gamescript *game::GameScript = 0;
157 valuemap game::GlobalValueMap;
158 dangermap game::DangerMap;
159 int game::NextDangerIDType;
160 int game::NextDangerIDConfigIndex;
161 characteridmap game::CharacterIDMap;
162 itemidmap game::ItemIDMap;
163 trapidmap game::TrapIDMap;
164 truth game::PlayerHurtByExplosion;
165 area *game::CurrentArea;
166 level *game::CurrentLevel;
167 wsquare ***game::CurrentWSquareMap;
168 lsquare ***game::CurrentLSquareMap;
169 festring game::DefaultPolymorphTo;
170 festring game::DefaultSummonMonster;
171 festring game::DefaultWish;
172 festring game::DefaultChangeMaterial;
173 festring game::DefaultDetectMaterial;
174 festring game::DefaultTeam;
175 truth game::WizardMode;
176 int game::SeeWholeMapCheatMode;
177 truth game::GoThroughWallsCheat;
178 int game::QuestMonstersFound;
179 bitmap *game::BusyAnimationCache[32];
180 festring game::PlayerName;
181 feuLong game::EquipmentMemory[MAX_EQUIPMENT_SLOTS];
182 olterrain *game::MonsterPortal;
183 std::vector<v2> game::SpecialCursorPos;
184 std::vector<int> game::SpecialCursorData;
185 cbitmap *game::EnterImage;
186 v2 game::EnterTextDisplacement;
189 // -1: none
190 int game::MoveVectorToDirection (cv2 &mv) {
191 for (int c = 0; c < 9; ++c) if (MoveVector[c] == mv) return c;
192 return -1;
196 char game::GetAbnormalMoveKey (int idx) {
197 if (idx < 0 || idx > 8) return 0;
198 return MoveAbnormalCommandKey[idx];
202 void game::SetAbnormalMoveKey (int idx, char ch) {
203 if (idx >= 0 && idx <= 8) MoveAbnormalCommandKey[idx] = ch;
207 void game::AddCharacterID (character *Char, feuLong ID) {
208 /*k8:??? if (CharacterIDMap.find(ID) != CharacterIDMap.end())
209 int esko = esko = 2;*/
210 CharacterIDMap.insert(std::make_pair(ID, Char));
214 void game::RemoveCharacterID (feuLong ID) {
215 /*k8:??? if (CharacterIDMap.find(ID) == CharacterIDMap.end())
216 int esko = esko = 2;*/
217 CharacterIDMap.erase(CharacterIDMap.find(ID));
221 void game::AddItemID (item *Item, feuLong ID) {
222 /*k8:??? if (ItemIDMap.find(ID) != ItemIDMap.end())
223 int esko = esko = 2;*/
224 ItemIDMap.insert(std::make_pair(ID, Item));
228 void game::RemoveItemID (feuLong ID) {
229 /*k8:??? if(ID && ItemIDMap.find(ID) == ItemIDMap.end())
230 int esko = esko = 2;*/
231 if (ID) ItemIDMap.erase(ItemIDMap.find(ID));
235 void game::UpdateItemID (item *Item, feuLong ID) {
236 /*k8:??? if(ItemIDMap.find(ID) == ItemIDMap.end())
237 int esko = esko = 2;*/
238 ItemIDMap.find(ID)->second = Item;
242 void game::AddTrapID (entity *Trap, feuLong ID) {
243 /*k8:??? if(TrapIDMap.find(ID) != TrapIDMap.end())
244 int esko = esko = 2;*/
245 if (ID) TrapIDMap.insert(std::make_pair(ID, Trap));
249 void game::RemoveTrapID (feuLong ID) {
250 /*k8:??? if(ID && TrapIDMap.find(ID) == TrapIDMap.end())
251 int esko = esko = 2;*/
252 if (ID) TrapIDMap.erase(TrapIDMap.find(ID));
256 void game::UpdateTrapID (entity *Trap, feuLong ID) {
257 /*k8:??? if(TrapIDMap.find(ID) == TrapIDMap.end())
258 int esko = esko = 2;*/
259 TrapIDMap.find(ID)->second = Trap;
263 const dangermap &game::GetDangerMap () { return DangerMap; }
264 void game::ClearItemDrawVector () { ItemDrawVector.clear(); }
265 void game::ClearCharacterDrawVector () { CharacterDrawVector.clear(); }
268 void game::InitScript () {
269 TextInputFile ScriptFile(GetGameDir()+"script/dungeon.dat", &GlobalValueMap);
270 GameScript = new gamescript;
271 GameScript->ReadFrom(ScriptFile);
272 { /* additional dungeon files */
273 for (int f = 0; f <= 99; f++) {
274 char bnum[32];
275 sprintf(bnum, "script/dungeon_%02d.dat", f);
276 TextInputFile ifl(game::GetGameDir()+bnum, &game::GetGlobalValueMap(), false);
277 if (ifl.IsOpen()) {
278 //fprintf(stderr, "loading: %s\n", bnum+7);
279 GameScript->ReadFrom(ifl);
280 ifl.Close();
284 GameScript->RandomizeLevels();
288 truth game::Init (cfestring &Name) {
289 if (Name.IsEmpty()) {
290 if (ivanconfig::GetDefaultName().IsEmpty()) {
291 PlayerName.Empty();
292 if (iosystem::StringQuestion(PlayerName, CONST_S("What is your name? (1-20 letters)"), v2(30, 46), WHITE, 1, 20, true, true) == ABORTED || PlayerName.IsEmpty()) return false;
293 } else {
294 PlayerName = ivanconfig::GetDefaultName();
296 } else {
297 PlayerName = Name;
300 mkdir(GetSavePath().CStr(), S_IRWXU|S_IRWXG);
301 mkdir(GetBonePath().CStr(), S_IRWXU|S_IRWXG);
303 ::InitPlaces();
304 LOSTick = 2;
305 DangerFound = 0;
306 CausePanicFlag = false;
307 pool::KillBees();
308 //???
309 switch (Load(SaveName(PlayerName))) {
310 case LOADED: {
311 globalwindowhandler::InstallControlLoop(AnimationController);
312 SetIsRunning(true);
313 SetForceJumpToPlayerBe(true);
314 GetCurrentArea()->SendNewDrawRequest();
315 SendLOSUpdateRequest();
316 ADD_MESSAGE("Game loaded successfully.");
317 } return true;
318 case NEW_GAME: {
319 iosystem::TextScreen(CONST_S(
320 "You couldn't possibly have guessed this day would differ from any other.\n"
321 "It began just as always. You woke up at dawn and drove off the giant spider\n"
322 "resting on your face. On your way to work you had serious trouble avoiding\n"
323 "the lions and pythons roaming wild around the village. After getting kicked\n"
324 "by colony masters for being late you performed your twelve-hour routine of\n"
325 "climbing trees, gathering bananas, climbing trees, gathering bananas, chasing\n"
326 "monkeys that stole the first gathered bananas, carrying bananas to the village\n"
327 "and trying to look happy when real food was distributed.\n\n"
328 "Finally you were about to enjoy your free time by taking a quick dip in the\n"
329 "nearby crocodile bay. However, at this point something unusual happened.\n"
330 "You were summoned to the mansion of Richel Decos, the viceroy of the\n"
331 "colony, and were led directly to him."));
332 iosystem::TextScreen(CONST_S(
333 "\"I have a task for you, citizen\", said the viceroy picking his golden\n"
334 "teeth, \"The market price of bananas has taken a deep dive and yet the\n"
335 "central government is about to raise taxes. I have sent appeals to high\n"
336 "priest Petrus but received no response. I fear my enemies in Attnam are\n"
337 "plotting against me and intercepting my messages before they reach him!\"\n\n"
338 "\"That is why you must travel to Attnam with a letter I'll give you and\n"
339 "deliver it to Petrus directly. Alas, you somehow have to cross the sea\n"
340 "between. Because it's winter, all Attnamese ships are trapped by ice and\n"
341 "I have none. Therefore you must venture through the small underwater tunnel\n"
342 "connecting our islands. It is infested with monsters, but since you have\n"
343 "stayed alive here so long, the trip will surely cause you no trouble.\"\n\n"
344 "You have never been so happy! According to the mansion's traveling\n"
345 "brochures, Attnam is a peaceful but bustling world city on a beautiful\n"
346 "snowy fell surrounded by frozen lakes glittering in the arctic sun just\n"
347 "like the diamonds of the imperial treasury. Not that you would believe a\n"
348 "word. The point is that tomorrow you can finally forget your home and\n"
349 "face the untold adventures ahead."));
350 pool::RemoveEverything(); // memory leak!
351 CurrentLevel = 0;
352 globalwindowhandler::InstallControlLoop(AnimationController);
353 LOSTick = 2;
354 DangerFound = 0;
355 CausePanicFlag = false;
356 SetIsRunning(true);
357 InWilderness = true;
358 iosystem::TextScreen(CONST_S("Generating game...\n\nThis may take some time, please wait."), ZERO_V2, WHITE, false, true, &BusyAnimation);
359 igraph::CreateBackGround(GRAY_FRACTAL);
360 NextCharacterID = 1;
361 NextItemID = 1;
362 NextTrapID = 1;
363 InitScript();
364 CreateTeams();
365 CreateGods();
366 SetPlayer(playerkind::Spawn());
367 Player->SetAssignedName(PlayerName);
368 Player->SetTeam(GetTeam(PLAYER_TEAM));
369 Player->SetNP(SATIATED_LEVEL);
370 for (int c = 0; c < ATTRIBUTES; ++c) {
371 if (c != ENDURANCE) Player->EditAttribute(c, (RAND()&1)-(RAND()&1));
372 Player->EditExperience(c, 500, 1<<11);
374 Player->SetMoney(Player->GetMoney()+RAND()%11);
375 GetTeam(0)->SetLeader(Player);
376 InitDangerMap();
378 pool::KillBees();
379 if (Player->IsEnabled()) { Player->Disable(); Player->Enable(); }
381 Petrus = 0;
382 InitDungeons();
383 SetCurrentArea(WorldMap = new worldmap(128, 128));
384 CurrentWSquareMap = WorldMap->GetMap();
385 WorldMap->Generate();
386 UpdateCamera();
387 SendLOSUpdateRequest();
388 Tick = 0;
389 Turn = 0;
390 InitPlayerAttributeAverage();
391 StoryState = 0;
392 /* */
393 XinrochTombStoryState = 0;
394 MondedrPass = 0;
395 RingOfThieves = 0;
396 Masamune = 0;
397 Muramasa = 0;
398 LoricatusHammer = 0;
399 Liberator = 0;
400 OmmelBloodMission = 0;
401 RegiiTalkState = 0;
402 /* */
403 PlayerMassacreMap.clear();
404 PetMassacreMap.clear();
405 MiscMassacreMap.clear();
406 PlayerMassacreAmount = PetMassacreAmount = MiscMassacreAmount = 0;
407 DefaultPolymorphTo.Empty();
408 DefaultSummonMonster.Empty();
409 DefaultWish.Empty();
410 DefaultChangeMaterial.Empty();
411 DefaultDetectMaterial.Empty();
412 DefaultTeam.Empty();
413 Player->GetStack()->AddItem(encryptedscroll::Spawn());
414 if (ivanconfig::GetDefaultPetName() != "_none_") {
415 character *Doggie = dog::Spawn();
416 Doggie->SetTeam(GetTeam(PLAYER_TEAM));
417 GetWorldMap()->GetPlayerGroup().push_back(Doggie);
418 Doggie->SetAssignedName(ivanconfig::GetDefaultPetName());
420 WizardMode = false;
421 SeeWholeMapCheatMode = MAP_HIDDEN;
422 GoThroughWallsCheat = false;
423 SumoWrestling = false;
424 GlobalRainTimeModifier = 2048-(RAND()&4095);
425 PlayerSumoChampion = false;
426 PlayerSolicitusChampion = false;
427 protosystem::InitCharacterDataBaseFlags();
428 memset(EquipmentMemory, 0, sizeof(EquipmentMemory));
429 PlayerRunning = false;
430 InitAttributeMemory();
431 NecroCounter = 0;
432 GameBegan = time(0);
433 LastLoad = time(0);
434 TimePlayedBeforeLastLoad = time::GetZeroTime();
435 /*k8: damn! seems that this is field, not local! bool PlayerHasReceivedAllGodsKnownBonus = false; */
436 PlayerHasReceivedAllGodsKnownBonus = false;
437 ADD_MESSAGE("You commence your journey to Attnam. Use direction keys to move, '>' to enter an area and '?' to view other commands.");
438 game::ClearEventData();
439 RunOnEvent("game_start");
440 if (IsXMas()) {
441 item *Present = banana::Spawn();
442 Player->GetStack()->AddItem(Present);
443 ADD_MESSAGE("Atavus is happy today! He gives you %s.", Present->CHAR_NAME(INDEFINITE));
445 } return true;
446 default: return false;
451 void game::DeInit () {
452 pool::BurnHell();
453 delete WorldMap;
454 WorldMap = 0;
455 if (Dungeon) {
456 for (int c = 1; c < Dungeons; ++c) delete Dungeon[c];
457 delete [] Dungeon;
458 Dungeon = 0;
460 if (God) {
461 for (int c = 1; c <= GODS; ++c) delete God[c]; // sorry, Valpuri!
462 delete [] God;
463 God = 0;
465 if (Team) {
466 for (int c = 0; c < Teams; ++c) delete Team[c];
467 delete [] Team;
468 Team = 0;
470 delete GameScript;
471 GameScript = 0;
472 msgsystem::Format();
473 DangerMap.clear();
477 void game::Run () {
478 for (;;) {
479 if (!InWilderness) {
480 /* Temporary places */
481 static int Counter = 0;
482 if (++Counter == 10) {
483 CurrentLevel->GenerateMonsters();
484 Counter = 0;
486 if (CurrentDungeonIndex == ELPURI_CAVE && CurrentLevelIndex == ZOMBIE_LEVEL && !RAND_N(1000+NecroCounter)) {
487 character *Char = necromancer::Spawn(RAND_N(4) ? APPRENTICE_NECROMANCER : MASTER_NECROMANCER);
488 v2 Pos;
489 for (int c2 = 0; c2 < 30; ++c2) {
490 Pos = GetCurrentLevel()->GetRandomSquare(Char);
491 if (abs(int(Pos.X)-Player->GetPos().X) > 20 || abs(int(Pos.Y)-Player->GetPos().Y) > 20) break;
493 if (Pos != ERROR_V2) {
494 Char->SetTeam(GetTeam(MONSTER_TEAM));
495 Char->PutTo(Pos);
496 Char->SetGenerationDanger(GetCurrentLevel()->GetDifficulty());
497 Char->SignalGeneration();
498 Char->SignalNaturalGeneration();
499 ivantime Time;
500 GetTime(Time);
501 int Modifier = Time.Day - EDIT_ATTRIBUTE_DAY_MIN;
502 if (Modifier > 0) Char->EditAllAttributes(Modifier >> EDIT_ATTRIBUTE_DAY_SHIFT);
503 NecroCounter += 50;
504 } else {
505 delete Char;
506 //Char->SendToHell(); // k8:equipment
510 if (!(GetTick() % 1000)) CurrentLevel->CheckSunLight();
512 if ((CurrentDungeonIndex == NEW_ATTNAM || CurrentDungeonIndex == ATTNAM) && CurrentLevelIndex == 0) {
513 sLong OldVolume = GlobalRainLiquid->GetVolume();
514 sLong NewVolume = Max(sLong(sin((Tick+GlobalRainTimeModifier)*0.0003)*300-150), 0);
515 if (NewVolume && !OldVolume) CurrentLevel->EnableGlobalRain();
516 else if(!NewVolume && OldVolume) CurrentLevel->DisableGlobalRain();
517 GlobalRainLiquid->SetVolumeNoSignals(NewVolume);
520 item *Item;
521 if (!RAND_N(2)) Item = wand::Spawn(1 + RAND_N(12));
522 else if(!RAND_N(2)) {
523 Item = beartrap::Spawn();
524 Item->SetIsActive(true);
525 Item->SetTeam(MONSTER_TEAM);
526 } else if(!RAND_N(2)) {
527 Item = mine::Spawn();
528 Item->SetIsActive(true);
529 Item->SetTeam(MONSTER_TEAM);
530 } else Item = holybanana::Spawn();
531 CurrentLevel->GetLSquare(CurrentLevel->GetRandomSquare())->AddItem(Item);
534 if(!RAND_N(10)) {
535 character *Char = protosystem::CreateMonster(0, 1000000);
536 Char->ChangeTeam(GetTeam(RAND() % Teams));
537 Char->PutTo(CurrentLevel->GetRandomSquare(Char));
540 if (!RAND_N(5)) {
541 character *Char;
542 if (!RAND_N(5)) Char = spider::Spawn(GIANT);
543 else if (!RAND_N(5)) Char = darkmage::Spawn(1 + RAND_N(4));
544 else if (!RAND_N(5)) Char = necromancer::Spawn(1 + RAND_N(2));
545 else if (!RAND_N(5)) Char = chameleon::Spawn();
546 else if (!RAND_N(5)) Char = kamikazedwarf::Spawn(1 + RAND_N(GODS));
547 else if (!RAND_N(5)) Char = mommo::Spawn(1 + RAND_N(2));
548 else if (!RAND_N(3)) Char = bunny::Spawn(RAND_2 ? ADULT_MALE : ADULT_FEMALE);
549 else if (!RAND_N(3)) Char = eddy::Spawn();
550 else if (!RAND_N(3)) Char = magicmushroom::Spawn();
551 else if (!RAND_N(5)) Char = mushroom::Spawn();
552 else if (!RAND_N(3)) Char = blinkdog::Spawn();
553 else if (!RAND_N(5)) Char = tourist::Spawn(1 + RAND_N(3));
554 else if (!RAND_N(5)) Char = hattifattener::Spawn();
555 else if (!RAND_N(5)) Char = genetrixvesana::Spawn();
556 else if (!RAND_N(5)) Char = skunk::Spawn();
557 else if (!RAND_N(5)) Char = ennerbeast::Spawn();
558 else if (!RAND_N(5)) Char = werewolfhuman::Spawn();
559 else if (!RAND_N(5)) Char = unicorn::Spawn(1 + RAND_N(3));
560 else if (!RAND_N(5)) Char = floatingeye::Spawn();
561 else if (!RAND_N(5)) Char = zombie::Spawn();
562 else if (!RAND_N(5)) Char = magpie::Spawn();
563 else if (!RAND_N(5)) Char = elpuri::Spawn();
564 else if (!RAND_N(5)) Char = vladimir::Spawn();
565 else if (!RAND_N(5)) Char = billswill::Spawn();
566 else if (!RAND_N(5)) Char = ghost::Spawn();
567 else if (!RAND_N(5)) Char = dolphin::Spawn();
568 else if (!RAND_N(5)) Char = cossack::Spawn();
569 else Char = invisiblestalker::Spawn();
570 Char->SetTeam(GetTeam(RAND() % Teams));
571 Char->PutTo(CurrentLevel->GetRandomSquare(Char));
577 try {
578 pool::Be();
579 pool::BurnHell();
580 IncreaseTick();
581 ApplyDivineTick();
582 } catch (quitrequest) {
583 break;
584 } catch (areachangerequest) {
590 void game::InitLuxTable () {
591 if (!LuxTable) {
592 Alloc3D(LuxTable, 256, 33, 33);
593 for (int c = 0; c < 0x100; ++c)
594 for (int x = 0; x < 33; ++x)
595 for (int y = 0; y < 33; ++y) {
596 int X = x-16, Y = y-16;
597 LuxTable[c][x][y] = int(c/(double(X*X+Y*Y)/128+1));
599 atexit(DeInitLuxTable);
604 void game::DeInitLuxTable () {
605 delete [] LuxTable;
606 LuxTable = 0;
610 void game::UpdateCameraX () {
611 UpdateCameraX(Player->GetPos().X);
615 void game::UpdateCameraY () {
616 UpdateCameraY(Player->GetPos().Y);
620 void game::UpdateCameraX (int X) {
621 UpdateCameraCoordinate(Camera.X, X, GetCurrentArea()->GetXSize(), GetScreenXSize());
625 void game::UpdateCameraY (int Y) {
626 UpdateCameraCoordinate(Camera.Y, Y, GetCurrentArea()->GetYSize(), GetScreenYSize());
630 void game::UpdateCameraCoordinate (int &Coordinate, int Center, int Size, int ScreenSize) {
631 int OldCoordinate = Coordinate;
632 if (Size < ScreenSize) Coordinate = (Size-ScreenSize)>>1;
633 else if(Center < ScreenSize>>1) Coordinate = 0;
634 else if(Center > Size-(ScreenSize>>1)) Coordinate = Size-ScreenSize;
635 else Coordinate = Center-(ScreenSize>>1);
636 if (Coordinate != OldCoordinate) GetCurrentArea()->SendNewDrawRequest();
640 cchar *game::Insult () {
641 static const char *insults[19] = {
642 "moron",
643 "silly",
644 "idiot",
645 "airhead",
646 "jerk",
647 "dork",
648 "Mr. Mole",
649 "navastater",
650 "potatoes-for-eyes",
651 "lamer",
652 "mommo-for-brains",
653 "pinhead",
654 "stupid-headed person",
655 "software abuser",
656 "loser",
657 "peaballs",
658 "person-with-problems",
659 "unimportant user",
660 "hugger-mugger"
662 int n = RAND_N(18);
663 if (n < 0 || n > 18) n = 18;
664 return insults[n];
668 /* DefaultAnswer = REQUIRES_ANSWER the question requires an answer */
669 truth game::TruthQuestion (cfestring &String, int DefaultAnswer, int OtherKeyForTrue) {
670 festring xstr = String;
671 if (DefaultAnswer == NO) { DefaultAnswer = 'n'; xstr << " [\1Cy\2/\1RN\2]"; }
672 else if (DefaultAnswer == YES) { DefaultAnswer = 'y'; xstr << " [\1RY\2/\1Cn\2]"; }
673 else if (DefaultAnswer == REQUIRES_ANSWER) { xstr << " [\1Cy\2/\1Cn\2]"; }
674 else ABORT("Illegal TruthQuestion DefaultAnswer send!");
675 int FromKeyQuestion = KeyQuestion(/*String*/xstr, DefaultAnswer, 9, 'y', 'Y', 'n', 'N', 't', 'T', 'o', 'O', OtherKeyForTrue);
676 return
677 FromKeyQuestion == 'y' || FromKeyQuestion == 'Y' ||
678 FromKeyQuestion == 't' || FromKeyQuestion == 'T' ||
679 FromKeyQuestion == OtherKeyForTrue;
683 void game::DrawEverything () {
684 DrawEverythingNoBlit();
685 graphics::BlitDBToScreen();
689 truth game::OnScreen (v2 Pos) {
690 return Pos.X >= 0 && Pos.Y >= 0 && Pos.X >= Camera.X && Pos.Y >= Camera.Y && Pos.X < GetCamera().X + GetScreenXSize() && Pos.Y < GetCamera().Y + GetScreenYSize();
694 void game::DrawEverythingNoBlit (truth AnimationDraw) {
695 if (LOSUpdateRequested && Player->IsEnabled()) {
696 if (!IsInWilderness()) GetCurrentLevel()->UpdateLOS(); else GetWorldMap()->UpdateLOS();
699 if (OnScreen(CursorPos)) {
700 if (!IsInWilderness() || CurrentWSquareMap[CursorPos.X][CursorPos.Y]->GetLastSeen() || GetSeeWholeMapCheatMode())
701 CurrentArea->GetSquare(CursorPos)->SendStrongNewDrawRequest();
702 else
703 DOUBLE_BUFFER->Fill(CalculateScreenCoordinates(CursorPos), TILE_V2, 0);
706 for (unsigned int c = 0; c < SpecialCursorPos.size(); ++c) {
707 if (OnScreen(SpecialCursorPos[c])) CurrentArea->GetSquare(SpecialCursorPos[c])->SendStrongNewDrawRequest();
710 globalwindowhandler::UpdateTick();
711 GetCurrentArea()->Draw(AnimationDraw);
712 Player->DrawPanel(AnimationDraw);
714 if (!AnimationDraw) msgsystem::Draw();
716 if (OnScreen(CursorPos)) {
717 v2 ScreenCoord = CalculateScreenCoordinates(CursorPos);
718 blitdata B = {
719 DOUBLE_BUFFER,
720 { 0, 0 },
721 { ScreenCoord.X, ScreenCoord.Y },
722 { TILE_SIZE, TILE_SIZE },
723 { 0 },
724 TRANSPARENT_COLOR,
725 ALLOW_ANIMATE|ALLOW_ALPHA
728 if (!IsInWilderness() && !GetSeeWholeMapCheatMode()) {
729 lsquare *Square = CurrentLSquareMap[CursorPos.X][CursorPos.Y];
730 if (Square->GetLastSeen() != GetLOSTick()) Square->DrawMemorized(B);
733 if (DoZoom()) {
734 B.Src = B.Dest;
735 B.Dest.X = RES.X - 96;
736 B.Dest.Y = RES.Y - 96;
737 B.Stretch = 5;
738 DOUBLE_BUFFER->StretchBlit(B);
741 igraph::DrawCursor(ScreenCoord, CursorData);
744 if (Player->IsEnabled()) {
745 if (Player->IsSmall()) {
746 v2 Pos = Player->GetPos();
747 if (OnScreen(Pos)) {
748 v2 ScreenCoord = CalculateScreenCoordinates(Pos);
749 igraph::DrawCursor(ScreenCoord, Player->GetCursorData());
751 } else {
752 for (int f = 0; f < Player->GetSquaresUnder(); ++f) {
753 v2 Pos = Player->GetPos(f);
754 if (OnScreen(Pos)) {
755 v2 ScreenCoord = CalculateScreenCoordinates(Pos);
756 igraph::DrawCursor(ScreenCoord, Player->GetCursorData()|CURSOR_BIG, f);
762 for (unsigned int c = 0; c < SpecialCursorPos.size(); ++c) {
763 if (OnScreen(SpecialCursorPos[c])) {
764 v2 ScreenCoord = CalculateScreenCoordinates(SpecialCursorPos[c]);
765 igraph::DrawCursor(ScreenCoord, SpecialCursorData[c]);
766 GetCurrentArea()->GetSquare(SpecialCursorPos[c])->SendStrongNewDrawRequest();
772 truth game::Save (cfestring &SaveName) {
773 if (!GetCurrentArea()->GetSquare(Player->GetPos())->GetCharacter()) {
774 Menu(0, v2(RES.X >> 1, RES.Y >> 1), CONST_S("Sorry, can't save due to I.V.A.N. bug.\r"), CONST_S("Continue\r"), LIGHT_GRAY);
775 return false;
777 DrawEverythingNoBlit();
778 #if defined(SGAME_SHOTS_IPU) || (!defined(HAVE_IMLIB2) && !defined(HAVE_LIBPNG))
779 DOUBLE_BUFFER->SaveScaledIPU(SaveName+".ipu", 0.8); //640; 320
780 #else
781 DOUBLE_BUFFER->SaveScaledPNG(SaveName+".png", 0.8); //640; 320
782 #endif
783 outputfile SaveFile(SaveName+".sav", ivanconfig::GetUseMaximumCompression());
784 SaveFile << int(SAVE_FILE_VERSION);
785 SaveFile << GameScript << CurrentDungeonIndex << CurrentLevelIndex << Camera;
786 SaveFile << WizardMode << SeeWholeMapCheatMode << GoThroughWallsCheat;
787 SaveFile << Tick << Turn << InWilderness << NextCharacterID << NextItemID << NextTrapID << NecroCounter;
788 SaveFile << SumoWrestling << PlayerSumoChampion << GlobalRainTimeModifier;
789 SaveFile << PlayerSolicitusChampion;
790 //sLong Seed = RAND();
791 //femath::SetSeed(Seed);
792 //SaveFile << Seed;
793 femath::SavePRNG(SaveFile);
794 SaveFile << AveragePlayerArmStrengthExperience;
795 SaveFile << AveragePlayerLegStrengthExperience;
796 SaveFile << AveragePlayerDexterityExperience;
797 SaveFile << AveragePlayerAgilityExperience;
798 SaveFile << Teams << Dungeons << StoryState << PlayerRunning;
799 SaveFile << MondedrPass << RingOfThieves << Masamune << Muramasa << LoricatusHammer << Liberator;
800 SaveFile << OmmelBloodMission << RegiiTalkState << XinrochTombStoryState;
801 SaveFile << PlayerMassacreMap << PetMassacreMap << MiscMassacreMap;
802 SaveFile << PlayerMassacreAmount << PetMassacreAmount << MiscMassacreAmount;
803 SaveArray(SaveFile, EquipmentMemory, MAX_EQUIPMENT_SLOTS);
804 for (int c = 0; c < ATTRIBUTES; ++c) SaveFile << OldAttribute[c] << NewAttribute[c] << LastAttributeChangeTick[c];
805 for (int c = 1; c < Dungeons; ++c) SaveFile << Dungeon[c];
806 for (int c = 1; c <= GODS; ++c) SaveFile << God[c];
807 for (int c = 0; c < Teams; ++c) SaveFile << Team[c];
808 if (InWilderness) {
809 SaveWorldMap(SaveName, false);
810 } else {
811 GetCurrentDungeon()->SaveLevel(SaveName, CurrentLevelIndex, false);
813 SaveFile << Player->GetPos() << PlayerName;
814 msgsystem::Save(SaveFile);
815 SaveFile << DangerMap << NextDangerIDType << NextDangerIDConfigIndex;
816 SaveFile << DefaultPolymorphTo << DefaultSummonMonster;
817 SaveFile << DefaultWish << DefaultChangeMaterial << DefaultDetectMaterial << DefaultTeam;
818 SaveFile << GetTimeSpent();
819 /* or in more readable format: time() - LastLoad + TimeAtLastLoad */
820 SaveFile << PlayerHasReceivedAllGodsKnownBonus;
821 protosystem::SaveCharacterDataBaseFlags(SaveFile);
822 return true;
826 int game::Load (cfestring &SaveName) {
827 inputfile SaveFile(SaveName+".sav", false);
828 if (!SaveFile.IsOpen()) return NEW_GAME;
829 int Version;
830 SaveFile >> Version;
831 if (Version != SAVE_FILE_VERSION) {
832 if (!iosystem::Menu(0, v2(RES.X >> 1, RES.Y >> 1), CONST_S("Sorry, this save is incompatible with the new version.\rStart new game?\r"), CONST_S("Yes\rNo\r"), LIGHT_GRAY)) {
833 return NEW_GAME;
834 } else {
835 return BACK;
838 SaveFile >> GameScript >> CurrentDungeonIndex >> CurrentLevelIndex >> Camera;
839 SaveFile >> WizardMode >> SeeWholeMapCheatMode >> GoThroughWallsCheat;
840 SaveFile >> Tick >> Turn >> InWilderness >> NextCharacterID >> NextItemID >> NextTrapID >> NecroCounter;
841 SaveFile >> SumoWrestling >> PlayerSumoChampion >> GlobalRainTimeModifier;
842 SaveFile >> PlayerSolicitusChampion;
843 //femath::SetSeed(ReadType(sLong, SaveFile));
844 femath::LoadPRNG(SaveFile);
845 SaveFile >> AveragePlayerArmStrengthExperience;
846 SaveFile >> AveragePlayerLegStrengthExperience;
847 SaveFile >> AveragePlayerDexterityExperience;
848 SaveFile >> AveragePlayerAgilityExperience;
849 SaveFile >> Teams >> Dungeons >> StoryState >> PlayerRunning;
850 SaveFile >> MondedrPass >> RingOfThieves >> Masamune >> Muramasa >> LoricatusHammer >> Liberator;
851 SaveFile >> OmmelBloodMission >> RegiiTalkState >> XinrochTombStoryState;
852 ; SaveFile >> PlayerMassacreMap >> PetMassacreMap >> MiscMassacreMap;
853 SaveFile >> PlayerMassacreAmount >> PetMassacreAmount >> MiscMassacreAmount;
854 LoadArray(SaveFile, EquipmentMemory, MAX_EQUIPMENT_SLOTS);
855 for (int c = 0; c < ATTRIBUTES; ++c) SaveFile >> OldAttribute[c] >> NewAttribute[c] >> LastAttributeChangeTick[c];
856 Dungeon = new dungeon*[Dungeons];
857 Dungeon[0] = 0;
858 for (int c = 1; c < Dungeons; ++c) SaveFile >> Dungeon[c];
859 God = new god*[GODS+1];
860 God[0] = 0;
861 for (int c = 1; c <= GODS; ++c) SaveFile >> God[c];
862 Team = new team*[Teams];
863 for (int c = 0; c < Teams; ++c) SaveFile >> Team[c];
864 if (InWilderness) {
865 SetCurrentArea(LoadWorldMap(SaveName));
866 CurrentWSquareMap = WorldMap->GetMap();
867 igraph::CreateBackGround(GRAY_FRACTAL);
868 } else {
869 SetCurrentArea(CurrentLevel = GetCurrentDungeon()->LoadLevel(SaveName, CurrentLevelIndex));
870 CurrentLSquareMap = CurrentLevel->GetMap();
871 igraph::CreateBackGround(*CurrentLevel->GetLevelScript()->GetBackGroundType());
873 v2 Pos;
874 SaveFile >> Pos >> PlayerName;
875 SetPlayer(GetCurrentArea()->GetSquare(Pos)->GetCharacter());
876 if (!PLAYER) {
877 DeInit();
878 if (!iosystem::Menu(0, v2(RES.X >> 1, RES.Y >> 1), CONST_S("Sorry, this save is broken due to bug in I.V.A.N.\rStart new game?\r"), CONST_S("Yes\rNo\r"), LIGHT_GRAY)) {
879 return NEW_GAME;
880 } else {
881 return BACK;
884 msgsystem::Load(SaveFile);
885 SaveFile >> DangerMap >> NextDangerIDType >> NextDangerIDConfigIndex;
886 SaveFile >> DefaultPolymorphTo >> DefaultSummonMonster;
887 SaveFile >> DefaultWish >> DefaultChangeMaterial >> DefaultDetectMaterial >> DefaultTeam;
888 SaveFile >> TimePlayedBeforeLastLoad;
889 SaveFile >> PlayerHasReceivedAllGodsKnownBonus;
890 LastLoad = time(0);
891 protosystem::LoadCharacterDataBaseFlags(SaveFile);
892 return LOADED;
896 festring game::SaveName (cfestring &Base) {
897 festring SaveName = GetSavePath();
898 if (!Base.GetSize()) SaveName << PlayerName; else SaveName << Base;
899 for (festring::sizetype c = 0; c < SaveName.GetSize(); ++c) if (SaveName[c] == ' ') SaveName[c] = '_';
900 return SaveName;
904 int game::GetMoveCommandKeyBetweenPoints (v2 A, v2 B) {
905 for (int c = 0; c < EXTENDED_DIRECTION_COMMAND_KEYS; ++c) {
906 if ((A + GetMoveVector(c)) == B) return GetMoveCommandKey(c);
908 return DIR_ERROR;
912 void game::ApplyDivineTick () {
913 for (int c = 1; c <= GODS; ++c) GetGod(c)->ApplyDivineTick();
917 void game::ApplyDivineAlignmentBonuses (god *CompareTarget, int Multiplier, truth Good) {
918 for (int c = 1; c <= GODS; ++c) if (GetGod(c) != CompareTarget) GetGod(c)->AdjustRelation(CompareTarget, Multiplier, Good);
922 v2 game::GetDirectionVectorForKey (int Key) {
923 if (Key == KEY_NUMPAD_5 || Key == '.') return ZERO_V2; /* k8: '.' */
924 for (int c = 0; c < EXTENDED_DIRECTION_COMMAND_KEYS; ++c) if (Key == GetMoveCommandKey(c)) return GetMoveVector(c);
925 return ERROR_V2;
929 double game::GetMinDifficulty () {
930 double Base = CurrentLevel->GetDifficulty()*0.2;
931 sLong MultiplierExponent = 0;
932 ivantime Time;
933 GetTime(Time);
934 int Modifier = Time.Day-DANGER_PLUS_DAY_MIN;
935 if (Modifier > 0) Base += DANGER_PLUS_MULTIPLIER * Modifier;
936 for (;;) {
937 int Dice = RAND()%25;
938 if (Dice < 5 && MultiplierExponent > -3) {
939 Base /= 3;
940 --MultiplierExponent;
941 continue;
943 if (Dice >= 20 && MultiplierExponent < 3) {
944 Base *= 3;
945 ++MultiplierExponent;
946 continue;
948 return Base;
953 void game::ShowLevelMessage () {
954 if (CurrentLevel->GetLevelMessage().GetSize()) ADD_MESSAGE("%s", CurrentLevel->GetLevelMessage().CStr());
955 CurrentLevel->SetLevelMessage("");
959 int game::DirectionQuestion (cfestring &Topic, truth RequireAnswer, truth AcceptYourself) {
960 for (;;) {
961 int Key = AskForKeyPress(Topic);
962 if (AcceptYourself && (Key == '.' || Key == KEY_NUMPAD_5)) return YOURSELF; //k8
963 for (int c = 0; c < DIRECTION_COMMAND_KEYS; ++c) if (Key == GetMoveCommandKey(c)) return c;
964 if (!RequireAnswer) return DIR_ERROR;
969 void game::RemoveSaves (truth RealSavesAlso) {
970 if (RealSavesAlso) {
971 remove(festring(SaveName()+".sav").CStr());
972 remove(festring(SaveName()+".wm").CStr());
973 remove(festring(SaveName()+".png").CStr());
974 remove(festring(SaveName()+".ipu").CStr());
976 remove(festring(AutoSaveFileName+".sav").CStr());
977 remove(festring(AutoSaveFileName+".wm").CStr());
978 remove(festring(AutoSaveFileName+".png").CStr());
979 remove(festring(AutoSaveFileName+".ipu").CStr());
980 festring File;
981 for (int i = 1; i < Dungeons; ++i) {
982 for (int c = 0; c < GetDungeon(i)->GetLevels(); ++c) {
983 /* This looks very odd. And it is very odd.
984 * Indeed, gcc is very odd to not compile this correctly with -O3
985 * if it is written in a less odd way. */
986 File = SaveName()+'.'+i;
987 File << c;
988 if (RealSavesAlso) remove(File.CStr());
989 File = AutoSaveFileName+'.'+i;
990 File << c;
991 remove(File.CStr());
997 void game::SetPlayer (character *NP) {
998 Player = NP;
999 if (Player) Player->AddFlags(C_PLAYER);
1003 void game::InitDungeons () {
1004 Dungeons = *GetGameScript()->GetDungeons()+1;
1005 //fprintf(stderr, "dungeon count: %d\n", Dungeons);
1006 Dungeon = new dungeon *[Dungeons];
1007 Dungeon[0] = 0;
1008 for (int c = 1; c < Dungeons; ++c) {
1009 Dungeon[c] = new dungeon(c);
1010 Dungeon[c]->SetIndex(c);
1015 void game::DoEvilDeed (int Amount) {
1016 if (!Amount) return;
1017 for (int c = 1; c <= GODS; ++c) {
1018 int Change = Amount-Amount*GetGod(c)->GetAlignment()/5;
1019 if (!IsInWilderness() && Player->GetLSquareUnder()->GetDivineMaster() == c) {
1020 if (GetGod(c)->GetRelation()-(Change << 1) < -750) {
1021 if (GetGod(c)->GetRelation() > -750) GetGod(c)->SetRelation(-750);
1022 } else if (GetGod(c)->GetRelation()-(Change << 1) > 750) {
1023 if (GetGod(c)->GetRelation() < 750) GetGod(c)->SetRelation(750);
1024 } else GetGod(c)->SetRelation(GetGod(c)->GetRelation()-(Change << 1));
1025 } else {
1026 if(GetGod(c)->GetRelation()-Change < -500) {
1027 if (GetGod(c)->GetRelation() > -500) GetGod(c)->SetRelation(-500);
1028 } else if (GetGod(c)->GetRelation()-Change > 500) {
1029 if (GetGod(c)->GetRelation() < 500) GetGod(c)->SetRelation(500);
1030 } else GetGod(c)->SetRelation(GetGod(c)->GetRelation() - Change);
1036 void game::SaveWorldMap (cfestring &SaveName, truth DeleteAfterwards) {
1037 outputfile SaveFile(SaveName+".wm", ivanconfig::GetUseMaximumCompression());
1038 SaveFile << WorldMap;
1039 if (DeleteAfterwards) {
1040 delete WorldMap;
1041 WorldMap = 0;
1046 worldmap *game::LoadWorldMap (cfestring &SaveName) {
1047 inputfile SaveFile(SaveName+".wm");
1048 SaveFile >> WorldMap;
1049 return WorldMap;
1053 void game::Hostility (team *Attacker, team *Defender) {
1054 for (int c = 0; c < Teams; ++c) {
1055 if (GetTeam(c) != Attacker && GetTeam(c) != Defender &&
1056 GetTeam(c)->GetRelation(Defender) == FRIEND &&
1057 c != NEW_ATTNAM_TEAM && c != TOURIST_GUIDE_TEAM) // gum solution
1058 GetTeam(c)->SetRelation(Attacker, HOSTILE);
1063 void game::CreateTeams () {
1064 Teams = *GetGameScript()->GetTeams();
1065 //fprintf(stderr, "team count: %d\n", Teams);
1066 Team = new team*[Teams];
1067 for (int c = 0; c < Teams; ++c) {
1068 Team[c] = new team(c);
1069 for (int i = 0; i < c; ++i) Team[i]->SetRelation(Team[c], UNCARING);
1071 for (int c = 0; c < Teams; ++c) if (c != MONSTER_TEAM) Team[MONSTER_TEAM]->SetRelation(Team[c], HOSTILE);
1072 const std::list<std::pair<int, teamscript> >& TeamScript = GetGameScript()->GetTeam();
1073 for (std::list<std::pair<int, teamscript> >::const_iterator i = TeamScript.begin(); i != TeamScript.end(); ++i) {
1074 for (uInt c = 0; c < i->second.GetRelation().size(); ++c) {
1075 GetTeam(i->second.GetRelation()[c].first)->SetRelation(GetTeam(i->first), i->second.GetRelation()[c].second);
1077 cint *KillEvilness = i->second.GetKillEvilness();
1078 if (KillEvilness) GetTeam(i->first)->SetKillEvilness(*KillEvilness);
1079 if (i->second.GetName()) GetTeam(i->first)->SetName(*i->second.GetName());
1084 team *game::FindTeam (cfestring &name) {
1085 for (int c = 0; c < Teams; ++c) {
1086 if (Team[c]->GetName().CompareIgnoreCase(name) == 0) return Team[c];
1088 return 0;
1092 /* v2 Pos should be removed from xxxQuestion()s? */
1093 festring game::StringQuestion (cfestring &Topic, col16 Color, festring::sizetype MinLetters, festring::sizetype MaxLetters, truth AllowExit, stringkeyhandler KeyHandler) {
1094 DrawEverythingNoBlit();
1095 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23)); // pos may be incorrect!
1096 festring Return;
1097 iosystem::StringQuestion(Return, Topic, v2(16, 6), Color, MinLetters, MaxLetters, false, AllowExit, KeyHandler);
1098 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1099 return Return;
1103 sLong game::NumberQuestion (cfestring &Topic, col16 Color, truth ReturnZeroOnEsc) {
1104 DrawEverythingNoBlit();
1105 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1106 sLong Return = iosystem::NumberQuestion(Topic, v2(16, 6), Color, false, ReturnZeroOnEsc);
1107 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1108 return Return;
1112 sLong game::ScrollBarQuestion (cfestring &Topic, sLong BeginValue, sLong Step, sLong Min, sLong Max, sLong AbortValue, col16 TopicColor, col16 Color1, col16 Color2, void (*Handler)(sLong)) {
1113 DrawEverythingNoBlit();
1114 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1115 sLong Return = iosystem::ScrollBarQuestion(Topic, v2(16, 6), BeginValue, Step, Min, Max, AbortValue, TopicColor, Color1, Color2, GetMoveCommandKey(KEY_LEFT_INDEX), GetMoveCommandKey(KEY_RIGHT_INDEX), false, Handler);
1116 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1117 return Return;
1121 feuLong game::IncreaseLOSTick () {
1122 if (LOSTick != 0xFE) return LOSTick += 2;
1123 CurrentLevel->InitLastSeen();
1124 return LOSTick = 4;
1128 void game::UpdateCamera () {
1129 UpdateCameraX();
1130 UpdateCameraY();
1134 truth game::HandleQuitMessage () {
1135 if (IsRunning()) {
1136 if (IsInGetCommand()) {
1137 switch (Menu(0, v2(RES.X >> 1, RES.Y >> 1), CONST_S("Do you want to save your game before quitting?\r"), CONST_S("Yes\rNo\rCancel\r"), LIGHT_GRAY)) {
1138 case 0:
1139 Save();
1140 RemoveSaves(false);
1141 break;
1142 case 2:
1143 GetCurrentArea()->SendNewDrawRequest();
1144 DrawEverything();
1145 return false;
1146 default:
1147 festring Msg = CONST_S("cowardly quit the game");
1148 Player->AddScoreEntry(Msg, 0.75);
1149 End(Msg, true, false);
1150 break;
1152 } else if (!Menu(0, v2(RES.X >> 1, RES.Y >> 1), CONST_S("You can't save at this point. Are you sure you still want to do this?\r"), CONST_S("Yes\rNo\r"), LIGHT_GRAY)) {
1153 RemoveSaves();
1154 } else {
1155 GetCurrentArea()->SendNewDrawRequest();
1156 DrawEverything();
1157 return false;
1160 return true;
1164 int game::GetDirectionForVector (v2 Vector) {
1165 for (int c = 0; c < DIRECTION_COMMAND_KEYS; ++c) if (Vector == GetMoveVector(c)) return c;
1166 return DIR_ERROR;
1170 cchar *game::GetVerbalPlayerAlignment () {
1171 sLong Sum = 0;
1172 for (int c = 1; c <= GODS; ++c) {
1173 if (GetGod(c)->GetRelation() > 0) Sum += GetGod(c)->GetRelation() * (5 - GetGod(c)->GetAlignment());
1175 if (Sum > 15000) return "extremely lawful";
1176 if (Sum > 10000) return "very lawful";
1177 if (Sum > 5000) return "lawful";
1178 if (Sum > 1000) return "mildly lawful";
1179 if (Sum > -1000) return "neutral";
1180 if (Sum > -5000) return "mildly chaotic";
1181 if (Sum > -10000) return "chaotic";
1182 if (Sum > -15000) return "very chaotic";
1183 return "extremely chaotic";
1187 void game::CreateGods () {
1188 God = new god*[GODS+1];
1189 God[0] = 0;
1190 for (int c = 1; c < protocontainer<god>::GetSize(); ++c) God[c] = protocontainer<god>::GetProto(c)->Spawn();
1194 void game::BusyAnimation () {
1195 BusyAnimation(DOUBLE_BUFFER, false);
1199 void game::BusyAnimation (bitmap *Buffer, truth ForceDraw) {
1200 static clock_t LastTime = 0;
1201 static int Frame = 0;
1202 static blitdata B1 = {
1204 { 0, 0 },
1205 { 0, 0 },
1206 { RES.X, RES.Y },
1207 { 0 },
1211 static blitdata B2 = {
1213 { 0, 0 },
1214 { (RES.X >> 1) - 100, (RES.Y << 1) / 3 - 100 },
1215 { 200, 200 },
1216 { 0 },
1220 if (ForceDraw || clock()-LastTime > CLOCKS_PER_SEC/25) {
1221 B2.Bitmap = Buffer;
1222 B2.Dest.X = (RES.X>>1)-100+EnterTextDisplacement.X;
1223 B2.Dest.Y = (RES.Y<<1)/3-100+EnterTextDisplacement.Y;
1224 if (EnterImage) {
1225 B1.Bitmap = Buffer;
1226 EnterImage->NormalMaskedBlit(B1);
1228 BusyAnimationCache[Frame]->NormalBlit(B2);
1229 if (Buffer == DOUBLE_BUFFER) graphics::BlitDBToScreen();
1230 if (++Frame == 32) Frame = 0;
1231 LastTime = clock();
1236 void game::CreateBusyAnimationCache () {
1237 bitmap Elpuri(TILE_V2, TRANSPARENT_COLOR);
1238 Elpuri.ActivateFastFlag();
1239 packcol16 Color = MakeRGB16(60, 60, 60);
1240 igraph::GetCharacterRawGraphic()->MaskedBlit(&Elpuri, v2(64, 0), ZERO_V2, TILE_V2, &Color);
1241 bitmap Circle(v2(200, 200), TRANSPARENT_COLOR);
1242 Circle.ActivateFastFlag();
1243 for (int x = 0; x < 4; ++x) Circle.DrawPolygon(100, 100, 95+x, 50, MakeRGB16(255-12*x, 0, 0));
1244 blitdata B1 = {
1246 { 0, 0 },
1247 { 92, 92 },
1248 { TILE_SIZE, TILE_SIZE },
1249 { 0 },
1250 TRANSPARENT_COLOR,
1253 blitdata B2 = {
1255 { 0, 0 },
1256 { 0, 0 },
1257 { 200, 200 },
1258 { 0 },
1259 TRANSPARENT_COLOR,
1262 for (int c = 0; c < 32; ++c) {
1263 B1.Bitmap = B2.Bitmap = BusyAnimationCache[c] = new bitmap(v2(200, 200), 0);
1264 B1.Bitmap->ActivateFastFlag();
1265 Elpuri.NormalMaskedBlit(B1);
1266 double Rotation = 0.3+c*FPI/80;
1267 for (int x = 0; x < 10; ++x) B1.Bitmap->DrawPolygon(100, 100, 95, 5, MakeRGB16(5+25*x, 0, 0), false, true, Rotation+double(x)/50);
1268 Circle.NormalMaskedBlit(B2);
1273 int game::AskForKeyPress (cfestring &Topic) {
1274 DrawEverythingNoBlit();
1275 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Topic.CapitalizeCopy().CStr());
1276 graphics::BlitDBToScreen();
1277 int Key = GET_KEY();
1278 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1279 return Key;
1283 void game::AskForEscPress (cfestring &Topic) {
1284 DrawEverythingNoBlit();
1285 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), RED/*WHITE*/, "%s [press ESC]", Topic.CapitalizeCopy().CStr());
1286 graphics::BlitDBToScreen();
1287 int Key;
1288 do {
1289 Key = GET_KEY();
1290 } while (Key != KEY_ESC);
1291 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1295 /* Handler is called when the key has been identified as a movement key
1296 * KeyHandler is called when the key has NOT been identified as a movement key
1297 * Both can be deactivated by passing 0 as parameter */
1298 v2 game::PositionQuestion (cfestring &Topic, v2 CursorPos, void (*Handler)(v2), positionkeyhandler KeyHandler, truth Zoom) {
1299 int Key = 0;
1300 SetDoZoom(Zoom);
1301 v2 Return;
1302 CursorData = RED_CURSOR;
1303 if (Handler) Handler(CursorPos);
1304 for (;;) {
1305 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1306 if (!Square->HasBeenSeen() &&
1307 (!Square->GetCharacter() || !Square->GetCharacter()->CanBeSeenByPlayer()) &&
1308 !GetSeeWholeMapCheatMode()) DOUBLE_BUFFER->Fill(CalculateScreenCoordinates(CursorPos), TILE_V2, BLACK);
1309 else GetCurrentArea()->GetSquare(CursorPos)->SendStrongNewDrawRequest();
1311 if (Key == ' ' || Key == '.' || Key == KEY_NUMPAD_5) { Return = CursorPos; break; }
1312 if (Key == KEY_ESC) { Return = ERROR_V2; break; }
1314 v2 DirectionVector = GetDirectionVectorForKey(Key);
1315 if (DirectionVector != ERROR_V2) {
1316 CursorPos += DirectionVector;
1317 if (CursorPos.X > GetCurrentArea()->GetXSize()-1) CursorPos.X = 0;
1318 if (CursorPos.X < 0) CursorPos.X = GetCurrentArea()->GetXSize()-1;
1319 if (CursorPos.Y > GetCurrentArea()->GetYSize()-1) CursorPos.Y = 0;
1320 if (CursorPos.Y < 0) CursorPos.Y = GetCurrentArea()->GetYSize()-1;
1321 if (Handler) Handler(CursorPos);
1322 } else if (KeyHandler) {
1323 CursorPos = KeyHandler(CursorPos, Key);
1324 if (CursorPos == ERROR_V2 || CursorPos == ABORT_V2) {
1325 Return = CursorPos;
1326 break;
1330 if (ivanconfig::GetAutoCenterMapOnLook()) {
1331 UpdateCameraX(CursorPos.X);
1332 UpdateCameraY(CursorPos.Y);
1333 } else {
1334 if (CursorPos.X < GetCamera().X+3 || CursorPos.X >= GetCamera().X+GetScreenXSize()-3) UpdateCameraX(CursorPos.X);
1335 if (CursorPos.Y < GetCamera().Y+3 || CursorPos.Y >= GetCamera().Y+GetScreenYSize()-3) UpdateCameraY(CursorPos.Y);
1338 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Topic.CStr());
1339 SetCursorPos(CursorPos);
1340 DrawEverything();
1341 Key = GET_KEY();
1344 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1345 igraph::BlitBackGround(v2(RES.X-96, RES.Y-96), v2(80, 80));
1346 SetDoZoom(false);
1347 SetCursorPos(v2(-1, -1));
1348 return Return;
1352 void game::LookHandler (v2 CursorPos) {
1353 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1354 festring OldMemory;
1356 if (GetSeeWholeMapCheatMode()) {
1357 OldMemory = Square->GetMemorizedDescription();
1358 if (IsInWilderness()) GetWorldMap()->GetWSquare(CursorPos)->UpdateMemorizedDescription(true);
1359 else GetCurrentLevel()->GetLSquare(CursorPos)->UpdateMemorizedDescription(true);
1362 festring Msg;
1363 if (Square->HasBeenSeen() || GetSeeWholeMapCheatMode()) {
1364 if (!IsInWilderness() && !Square->CanBeSeenByPlayer() && GetCurrentLevel()->GetLSquare(CursorPos)->CanBeFeltByPlayer())
1365 Msg = CONST_S("You feel here ");
1366 else if (Square->CanBeSeenByPlayer(true) || GetSeeWholeMapCheatMode())
1367 Msg = CONST_S("You see here ");
1368 else
1369 Msg = CONST_S("You remember here ");
1370 Msg << Square->GetMemorizedDescription() << '.';
1371 if (!IsInWilderness() && (Square->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) {
1372 lsquare *LSquare = GetCurrentLevel()->GetLSquare(CursorPos);
1373 LSquare->DisplaySmokeInfo(Msg);
1374 if (LSquare->HasEngravings() && LSquare->IsTransparent()) {
1375 if (LSquare->EngravingsCanBeReadByPlayer() || GetSeeWholeMapCheatMode()) LSquare->DisplayEngravedInfo(Msg);
1376 else Msg << " Something has been engraved here.";
1379 } else Msg = CONST_S("You have never been here.");
1380 character *Character = Square->GetCharacter();
1381 if (Character && (Character->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) Character->DisplayInfo(Msg);
1382 if (!(RAND()%10000) && (Square->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) Msg << " You see here a frog eating a magnolia.";
1383 ADD_MESSAGE("%s", Msg.CStr());
1384 if (GetSeeWholeMapCheatMode()) Square->SetMemorizedDescription(OldMemory);
1388 truth game::AnimationController () {
1389 DrawEverythingNoBlit(true);
1390 return true;
1394 void game::LoadGlobalValueMap (TextInput &fl) {
1395 festring word;
1396 fl.setGetVarCB(game::ldrGetVar);
1397 for (fl.ReadWord(word, false); !fl.Eof(); fl.ReadWord(word, false)) {
1398 if (word == "Include") {
1399 word = fl.ReadWord();
1400 if (fl.ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1401 //fprintf(stderr, "loading: %s\n", word.CStr());
1402 TextInputFile incf(game::GetGameDir()+"script/"+word, &game::GetGlobalValueMap());
1403 LoadGlobalValueMap(incf);
1404 continue;
1406 if (word == "Message") {
1407 word = fl.ReadWord();
1408 if (fl.ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1409 fprintf(stderr, "MESSAGE: %s\n", word.CStr());
1410 continue;
1412 if (word != "#") ABORT("Illegal datafile define in file %s on line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1413 fl.ReadWord(word, true);
1414 if (word == "enum" || word == "bitenum") {
1415 truth isBit = word == "bitenum";
1416 sLong idx = 0;
1417 if (fl.ReadWord() != "{") ABORT("'{' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1418 festring idName;
1419 truth done = false;
1420 while (!done) {
1421 fl.ReadWord(word, true);
1422 if (word == "}") break;
1423 if (word == "=") {
1424 idName.Empty();
1425 } else {
1426 idName = word;
1427 fl.ReadWord(word, true);
1429 if (word == "=") {
1430 // set current index
1431 idx = fl.ReadNumber();
1432 } else {
1433 if (word != "," && word != ";" && word != "}") ABORT("',' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1434 if (word == "}") done = true;
1436 if (idName.GetSize() > 0) {
1437 sLong i = idx;
1438 if (isBit) i = 1<<i;
1439 GlobalValueMap.insert(std::make_pair(idName, i));
1440 idx++;
1443 fl.skipBlanks();
1444 int ch = fl.GetChar();
1445 if (ch != EOF && ch != ';') fl.UngetChar(ch);
1446 //if (fl.ReadWord() != ";") ABORT("';' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1447 continue;
1449 if (word == "define") {
1450 fl.ReadWord(word);
1451 sLong v = fl.ReadNumber();
1452 GlobalValueMap.insert(std::make_pair(word, v));
1453 continue;
1455 ABORT("Illegal datafile define in file %s on line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1460 truth game::HasGlobalValue (cfestring &name) {
1461 auto it = GlobalValueMap.find(name);
1462 return (it != GlobalValueMap.end());
1466 sLong game::FindGlobalValue (cfestring &name, sLong defval, truth* found) {
1467 auto it = GlobalValueMap.find(name);
1468 if (it != GlobalValueMap.end()) {
1469 if (found) *found = true;
1470 return it->second;
1471 } else {
1472 if (found) *found = false;
1473 return defval;
1478 sLong game::FindGlobalValue (cfestring &name, truth* found) {
1479 return FindGlobalValue(name, -1, found);
1483 // this will fail if there is no such constant
1484 //TODO: cache values
1485 sLong game::GetGlobalConst (cfestring &name) {
1486 auto it = GlobalValueMap.find(name);
1487 if (it == GlobalValueMap.end()) ABORT("Global constant '%s' not found!", name.CStr());
1488 return it->second;
1492 void game::InitGlobalValueMap () {
1493 TextInputFile SaveFile(GetGameDir()+"script/define.dat", &GlobalValueMap);
1494 LoadGlobalValueMap(SaveFile);
1495 { /* additional files */
1496 for (int f = 0; f <= 99; f++) {
1497 char bnum[32];
1498 sprintf(bnum, "script/define_%02d.dat", f);
1499 festring fn = game::GetGameDir();
1500 fn << bnum;
1501 if (inputfile::fileExists(fn)) return;
1502 TextInputFile ifl(fn, &game::GetGlobalValueMap(), false);
1503 if (ifl.IsOpen()) {
1504 LoadGlobalValueMap(ifl);
1505 ifl.Close();
1512 void game::TextScreen (cfestring &Text, v2 Displacement, col16 Color, truth GKey, truth Fade, bitmapeditor BitmapEditor) {
1513 globalwindowhandler::DisableControlLoops();
1514 iosystem::TextScreen(Text, Displacement, Color, GKey, Fade, BitmapEditor);
1515 globalwindowhandler::EnableControlLoops();
1519 /* ... all the keys that are acceptable
1520 DefaultAnswer = REQUIRES_ANSWER if this question requires an answer
1521 Not surprisingly KeyNumber is the number of keys at ...
1523 int game::KeyQuestion (cfestring &Message, int DefaultAnswer, int KeyNumber, ...) {
1524 int keybuf[256];
1525 int *Key = keybuf;
1526 va_list Arguments;
1527 va_start(Arguments, KeyNumber);
1528 for (int c = 0; c < KeyNumber; ++c) {
1529 if (c > 255) ABORT("Too many keys in `game::KeyQuestion()`");
1530 Key[c] = va_arg(Arguments, int);
1532 va_end(Arguments);
1533 DrawEverythingNoBlit();
1534 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Message.CStr());
1535 auto cursave = graphics::getCursorState(16+Message.rawLength()*8, 8+7, true);
1536 int Return = 0;
1537 while (!Return) {
1538 int k = GET_KEY();
1539 for (int c = 0; c < KeyNumber; ++c) {
1540 if (Key[c] == k) {
1541 Return = k;
1542 break;
1545 if (!Return && DefaultAnswer != REQUIRES_ANSWER) Return = DefaultAnswer;
1547 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1548 return Return;
1552 v2 game::LookKeyHandler (v2 CursorPos, int Key) {
1553 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1554 switch (Key) {
1555 case 'i':
1556 if (!IsInWilderness()) {
1557 if (Square->CanBeSeenByPlayer() || CursorPos == Player->GetPos() || GetSeeWholeMapCheatMode()) {
1558 lsquare *LSquare = GetCurrentLevel()->GetLSquare(CursorPos);
1559 stack *Stack = LSquare->GetStack();
1560 if (LSquare->IsTransparent() && Stack->GetVisibleItems(Player))
1561 Stack->DrawContents(Player, "Items here", NO_SELECT|(GetSeeWholeMapCheatMode() ? 0 : NO_SPECIAL_INFO));
1562 else
1563 ADD_MESSAGE("You see no items here.");
1564 } else ADD_MESSAGE("You should perhaps move a bit closer.");
1566 break;
1567 case 'c':
1568 if (Square->CanBeSeenByPlayer() || CursorPos == Player->GetPos() || GetSeeWholeMapCheatMode()) {
1569 character *Char = Square->GetCharacter();
1570 if (Char && (Char->CanBeSeenByPlayer() || Char->IsPlayer() || GetSeeWholeMapCheatMode()))
1571 Char->PrintInfo();
1572 else
1573 ADD_MESSAGE("You see no one here.");
1574 } else ADD_MESSAGE("You should perhaps move a bit closer.");
1575 break;
1577 return CursorPos;
1581 v2 game::NameKeyHandler (v2 CursorPos, int Key) {
1582 if (SelectPet(Key)) return LastPetUnderCursor->GetPos();
1583 if (Key == 'n' || Key == 'N') {
1584 character *Char = GetCurrentArea()->GetSquare(CursorPos)->GetCharacter();
1585 if (Char && Char->CanBeSeenByPlayer()) Char->TryToName();
1586 else ADD_MESSAGE("You don't see anyone here to name.");
1588 return CursorPos;
1592 void game::End (festring DeathMessage, truth Permanently, truth AndGoToMenu) {
1593 if (!Permanently || WizardModeIsReallyActive()) game::Save();
1594 pool::AbortBe();
1595 globalwindowhandler::DeInstallControlLoop(AnimationController);
1596 SetIsRunning(false);
1597 if (Permanently || !WizardModeIsReallyActive()) RemoveSaves(Permanently);
1598 if (Permanently && !WizardModeIsReallyActive()) {
1599 highscore HScore;
1600 if (HScore.LastAddFailed()) {
1601 iosystem::TextScreen(CONST_S("You didn't manage to get onto the high score list.\n\n\n\n")+GetPlayerName()+", "+DeathMessage+"\nRIP");
1602 } else {
1603 HScore.Draw();
1606 if (AndGoToMenu) {
1607 /* This prevents monster movement etc. after death. */
1608 throw quitrequest();
1613 int game::CalculateRoughDirection (v2 Vector) {
1614 if (!Vector.X && !Vector.Y) return YOURSELF;
1615 double Angle = femath::CalculateAngle(Vector);
1616 if (Angle < FPI / 8) return 4;
1617 else if (Angle < 3*FPI/8) return 7;
1618 else if (Angle < 5*FPI/8) return 6;
1619 else if (Angle < 7*FPI/8) return 5;
1620 else if (Angle < 9*FPI/8) return 3;
1621 else if (Angle < 11*FPI/8) return 0;
1622 else if (Angle < 13*FPI/8) return 1;
1623 else if (Angle < 15*FPI/8) return 2;
1624 else return 4;
1628 int game::Menu (bitmap *BackGround, v2 Pos, cfestring &Topic, cfestring &sMS, col16 Color, cfestring &SmallText1, cfestring &SmallText2) {
1629 globalwindowhandler::DisableControlLoops();
1630 int Return = iosystem::Menu(BackGround, Pos, Topic, sMS, Color, SmallText1, SmallText2);
1631 globalwindowhandler::EnableControlLoops();
1632 return Return;
1636 void game::InitDangerMap () {
1637 truth First = true;
1638 pool::RegisterState(false);
1639 //fprintf(stderr, "game::InitDangerMap(): START\n");
1640 for (int c1 = 1; c1 < protocontainer<character>::GetSize(); ++c1) {
1641 BusyAnimation();
1642 const character::prototype *Proto = protocontainer<character>::GetProto(c1);
1643 const character::database *const *ConfigData = Proto->GetConfigData();
1644 int ConfigSize = Proto->GetConfigSize();
1645 for (int c2 = 0; c2 < ConfigSize; ++c2) {
1646 if (!ConfigData[c2]->IsAbstract) {
1647 int Config = ConfigData[c2]->Config;
1648 if (First) {
1649 NextDangerIDType = c1;
1650 NextDangerIDConfigIndex = c2;
1651 First = false;
1653 character *Char = Proto->Spawn(Config, NO_EQUIPMENT|NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1654 double NakedDanger = Char->GetRelativeDanger(Player, true);
1655 //fprintf(stderr, " game::InitDangerMap(): 00\n");
1656 delete Char;
1657 //Char->SendToHell(); // k8: equipment
1658 Char = Proto->Spawn(Config, NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1659 double EquippedDanger = Char->GetRelativeDanger(Player, true);
1660 //fprintf(stderr, " game::InitDangerMap(): 01\n");
1661 delete Char;
1662 //Char->SendToHell(); // k8: equipment
1663 DangerMap[configid(c1, Config)] = dangerid(NakedDanger, EquippedDanger);
1667 pool::RegisterState(true);
1668 //fprintf(stderr, "game::InitDangerMap(): DONE\n");
1672 void game::CalculateNextDanger () {
1673 if (IsInWilderness() || !*CurrentLevel->GetLevelScript()->GenerateMonsters()) return;
1674 const character::prototype *Proto = protocontainer<character>::GetProto(NextDangerIDType);
1675 const character::database *const *ConfigData = Proto->GetConfigData();
1676 const character::database *DataBase = ConfigData[NextDangerIDConfigIndex];
1677 dangermap::iterator DangerIterator = DangerMap.find(configid(NextDangerIDType, DataBase->Config));
1678 team *Team = GetTeam(PLAYER_TEAM);
1679 if (DataBase && DangerIterator != DangerMap.end()) {
1680 //fprintf(stderr, "game::CalculateNextDanger(): START\n");
1681 pool::RegisterState(false);
1682 character *Char = Proto->Spawn(DataBase->Config, NO_EQUIPMENT|NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1683 std::list<character*>::const_iterator i;
1684 double DangerSum = Player->GetRelativeDanger(Char, true);
1685 for (i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i) {
1686 if ((*i)->IsEnabled() && !(*i)->IsTemporary() && !RAND_N(10)) DangerSum += (*i)->GetRelativeDanger(Char, true)/4;
1688 double CurrentDanger = 1/DangerSum;
1689 double NakedDanger = DangerIterator->second.NakedDanger;
1690 //fprintf(stderr, " game::CalculateNextDanger(): 00\n");
1691 delete Char;
1692 //Char->SendToHell(); // k8: equipment
1693 if (NakedDanger > CurrentDanger) DangerIterator->second.NakedDanger = (NakedDanger*9+CurrentDanger)/10;
1694 Char = Proto->Spawn(DataBase->Config, NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1695 DangerSum = Player->GetRelativeDanger(Char, true);
1696 for (i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i) {
1697 if ((*i)->IsEnabled() && !(*i)->IsTemporary() && !RAND_N(10)) DangerSum += (*i)->GetRelativeDanger(Char, true) / 4;
1699 CurrentDanger = 1/DangerSum;
1700 double EquippedDanger = DangerIterator->second.EquippedDanger;
1701 //fprintf(stderr, " game::CalculateNextDanger(): 01\n");
1702 delete Char;
1703 //Char->SendToHell(); // k8: equipment
1704 pool::RegisterState(true);
1705 if (EquippedDanger > CurrentDanger) DangerIterator->second.EquippedDanger = (EquippedDanger*9+CurrentDanger)/10;
1706 if (++NextDangerIDConfigIndex < Proto->GetConfigSize()) {
1707 //fprintf(stderr, "game::CalculateNextDanger(): EXIT0\n");
1708 return;
1710 for (;;) {
1711 if (++NextDangerIDType >= protocontainer<character>::GetSize()) NextDangerIDType = 1;
1712 Proto = protocontainer<character>::GetProto(NextDangerIDType);
1713 ConfigData = Proto->GetConfigData();
1714 int ConfigSize = Proto->GetConfigSize();
1715 for (int c = 0; c < ConfigSize; ++c) {
1716 if (!ConfigData[c]->IsAbstract) {
1717 NextDangerIDConfigIndex = c;
1718 //fprintf(stderr, "game::CalculateNextDanger(): EXIT1\n");
1719 return;
1723 //fprintf(stderr, "game::CalculateNextDanger(): DONE\n");
1724 } else {
1725 ABORT("It is dangerous to go ice fishing in the summer.");
1730 truth game::TryTravel (int Dungeon, int Area, int EntryIndex, truth AllowHostiles, truth AlliesFollow) {
1731 charactervector Group;
1732 if (LeaveArea(Group, AllowHostiles, AlliesFollow)) {
1733 CurrentDungeonIndex = Dungeon;
1734 EnterArea(Group, Area, EntryIndex);
1735 return true;
1737 return false;
1741 truth game::LeaveArea (charactervector &Group, truth AllowHostiles, truth AlliesFollow) {
1742 if (!IsInWilderness()) {
1743 if (AlliesFollow && !GetCurrentLevel()->CollectCreatures(Group, Player, AllowHostiles)) return false;
1744 Player->Remove();
1745 GetCurrentDungeon()->SaveLevel(SaveName(), CurrentLevelIndex);
1746 } else {
1747 Player->Remove();
1748 GetWorldMap()->GetPlayerGroup().swap(Group);
1749 SaveWorldMap();
1751 return true;
1755 /* Used always when the player enters an area. */
1756 void game::EnterArea (charactervector &Group, int Area, int EntryIndex) {
1757 if (Area != WORLD_MAP) {
1758 Generating = true;
1759 SetIsInWilderness(false);
1760 CurrentLevelIndex = Area;
1761 truth New = !PrepareRandomBone(Area) && !GetCurrentDungeon()->PrepareLevel(Area);
1762 igraph::CreateBackGround(*CurrentLevel->GetLevelScript()->GetBackGroundType());
1763 GetCurrentArea()->SendNewDrawRequest();
1764 v2 Pos = GetCurrentLevel()->GetEntryPos(Player, EntryIndex);
1765 if (Player) {
1766 GetCurrentLevel()->GetLSquare(Pos)->KickAnyoneStandingHereAway();
1767 Player->PutToOrNear(Pos);
1768 } else SetPlayer(GetCurrentLevel()->GetLSquare(Pos)->GetCharacter());
1769 uInt c;
1770 for (c = 0; c < Group.size(); ++c) {
1771 v2 NPCPos = GetCurrentLevel()->GetNearestFreeSquare(Group[c], Pos);
1772 if (NPCPos == ERROR_V2) NPCPos = GetCurrentLevel()->GetRandomSquare(Group[c]);
1773 Group[c]->PutTo(NPCPos);
1775 GetCurrentLevel()->FiatLux();
1776 ctruth *AutoReveal = GetCurrentLevel()->GetLevelScript()->AutoReveal();
1777 if (New && AutoReveal && *AutoReveal) GetCurrentLevel()->Reveal();
1778 ShowLevelMessage();
1779 SendLOSUpdateRequest();
1780 UpdateCamera();
1782 /* Gum solution! */
1783 if (New && CurrentDungeonIndex == ATTNAM && Area == 0) {
1784 GlobalRainLiquid = powder::Spawn(SNOW);
1785 GlobalRainSpeed = v2(-64, 128);
1786 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
1789 if (New && CurrentDungeonIndex == NEW_ATTNAM && Area == 0) {
1790 GlobalRainLiquid = liquid::Spawn(WATER);
1791 GlobalRainSpeed = v2(256, 512);
1792 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
1795 if (New && CurrentDungeonIndex == ELPURI_CAVE && Area == OREE_LAIR) {
1796 GlobalRainLiquid = liquid::Spawn(BLOOD);
1797 GlobalRainSpeed = v2(256, 512);
1798 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
1799 GlobalRainLiquid->SetVolumeNoSignals(200);
1800 CurrentLevel->EnableGlobalRain();
1803 if (New && CurrentDungeonIndex == MUNTUO && Area == 0) {
1804 GlobalRainLiquid = liquid::Spawn(WATER);
1805 GlobalRainSpeed = v2(-64, 1024);
1806 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
1809 Generating = false;
1810 GetCurrentLevel()->UpdateLOS();
1811 Player->SignalStepFrom(0);
1813 for (c = 0; c < Group.size(); ++c) Group[c]->SignalStepFrom(0);
1815 if (ivanconfig::GetAutoSaveInterval()) Save(GetAutoSaveFileName().CStr());
1816 } else {
1817 igraph::CreateBackGround(GRAY_FRACTAL);
1818 SetIsInWilderness(true);
1819 LoadWorldMap();
1820 SetCurrentArea(WorldMap);
1821 CurrentWSquareMap = WorldMap->GetMap();
1822 GetWorldMap()->GetPlayerGroup().swap(Group);
1823 Player->PutTo(GetWorldMap()->GetEntryPos(Player, EntryIndex));
1824 SendLOSUpdateRequest();
1825 UpdateCamera();
1826 GetWorldMap()->UpdateLOS();
1827 if (ivanconfig::GetAutoSaveInterval()) Save(GetAutoSaveFileName().CStr());
1832 int game::CompareLightToInt (col24 L, col24 Int) {
1833 if ((L & 0xFF0000) > Int || (L & 0xFF00) > Int || (L & 0xFF) > Int) return 1;
1834 if ((L & 0xFF0000) == Int || (L & 0xFF00) == Int || (L & 0xFF) == Int) return 0;
1835 return -1;
1839 void game::SetStandardListAttributes (felist &List) {
1840 List.SetPos(v2(26, 42));
1841 List.SetWidth(652);
1842 List.SetFlags(DRAW_BACKGROUND_AFTERWARDS);
1843 List.SetUpKey(GetMoveCommandKey(KEY_UP_INDEX));
1844 List.SetDownKey(GetMoveCommandKey(KEY_DOWN_INDEX));
1848 void game::InitPlayerAttributeAverage () {
1849 AveragePlayerArmStrengthExperience
1850 = AveragePlayerLegStrengthExperience
1851 = AveragePlayerDexterityExperience
1852 = AveragePlayerAgilityExperience
1853 = 0;
1855 if (!Player->IsHumanoid()) return;
1857 humanoid *Player = static_cast<humanoid*>(GetPlayer());
1858 int Arms = 0;
1859 int Legs = 0;
1860 arm *RightArm = Player->GetRightArm();
1862 if (RightArm && !RightArm->UseMaterialAttributes()) {
1863 AveragePlayerArmStrengthExperience += RightArm->GetStrengthExperience();
1864 AveragePlayerDexterityExperience += RightArm->GetDexterityExperience();
1865 ++Arms;
1868 arm *LeftArm = Player->GetLeftArm();
1870 if (LeftArm && !LeftArm->UseMaterialAttributes()) {
1871 AveragePlayerArmStrengthExperience += LeftArm->GetStrengthExperience();
1872 AveragePlayerDexterityExperience += LeftArm->GetDexterityExperience();
1873 ++Arms;
1876 leg *RightLeg = Player->GetRightLeg();
1878 if (RightLeg && !RightLeg->UseMaterialAttributes()) {
1879 AveragePlayerLegStrengthExperience += RightLeg->GetStrengthExperience();
1880 AveragePlayerAgilityExperience += RightLeg->GetAgilityExperience();
1881 ++Legs;
1884 leg *LeftLeg = Player->GetLeftLeg();
1886 if (LeftLeg && !LeftLeg->UseMaterialAttributes()) {
1887 AveragePlayerLegStrengthExperience += LeftLeg->GetStrengthExperience();
1888 AveragePlayerAgilityExperience += LeftLeg->GetAgilityExperience();
1889 ++Legs;
1892 if (Arms) {
1893 AveragePlayerArmStrengthExperience /= Arms;
1894 AveragePlayerDexterityExperience /= Arms;
1897 if (Legs) {
1898 AveragePlayerLegStrengthExperience /= Legs;
1899 AveragePlayerAgilityExperience /= Legs;
1904 void game::UpdatePlayerAttributeAverage () {
1905 if (!Player->IsHumanoid()) return;
1907 humanoid *Player = static_cast<humanoid*>(GetPlayer());
1908 double PlayerArmStrengthExperience = 0;
1909 double PlayerLegStrengthExperience = 0;
1910 double PlayerDexterityExperience = 0;
1911 double PlayerAgilityExperience = 0;
1912 int Arms = 0;
1913 int Legs = 0;
1914 arm *RightArm = Player->GetRightArm();
1916 if (RightArm && !RightArm->UseMaterialAttributes()) {
1917 PlayerArmStrengthExperience += RightArm->GetStrengthExperience();
1918 PlayerDexterityExperience += RightArm->GetDexterityExperience();
1919 ++Arms;
1922 arm *LeftArm = Player->GetLeftArm();
1924 if (LeftArm && !LeftArm->UseMaterialAttributes()) {
1925 PlayerArmStrengthExperience += LeftArm->GetStrengthExperience();
1926 PlayerDexterityExperience += LeftArm->GetDexterityExperience();
1927 ++Arms;
1930 leg *RightLeg = Player->GetRightLeg();
1932 if (RightLeg && !RightLeg->UseMaterialAttributes()) {
1933 PlayerLegStrengthExperience += RightLeg->GetStrengthExperience();
1934 PlayerAgilityExperience += RightLeg->GetAgilityExperience();
1935 ++Legs;
1938 leg *LeftLeg = Player->GetLeftLeg();
1940 if (LeftLeg && !LeftLeg->UseMaterialAttributes()) {
1941 PlayerLegStrengthExperience += LeftLeg->GetStrengthExperience();
1942 PlayerAgilityExperience += LeftLeg->GetAgilityExperience();
1943 ++Legs;
1946 if (Arms) {
1947 AveragePlayerArmStrengthExperience = (49 * AveragePlayerArmStrengthExperience + PlayerArmStrengthExperience / Arms) / 50;
1948 AveragePlayerDexterityExperience = (49 * AveragePlayerDexterityExperience + PlayerDexterityExperience / Arms) / 50;
1951 if (Legs) {
1952 AveragePlayerLegStrengthExperience = (49 * AveragePlayerLegStrengthExperience + PlayerLegStrengthExperience / Legs) / 50;
1953 AveragePlayerAgilityExperience = (49 * AveragePlayerAgilityExperience + PlayerAgilityExperience / Legs) / 50;
1958 void game::CallForAttention (v2 Pos, int RangeSquare) {
1959 for (int c = 0; c < GetTeams(); ++c) {
1960 if (GetTeam(c)->HasEnemy())
1961 for (std::list<character*>::const_iterator i = GetTeam(c)->GetMember().begin(); i != GetTeam(c)->GetMember().end(); ++i)
1962 if ((*i)->IsEnabled()) {
1963 sLong ThisDistance = HypotSquare(sLong((*i)->GetPos().X) - Pos.X, sLong((*i)->GetPos().Y) - Pos.Y);
1964 if (ThisDistance <= RangeSquare && !(*i)->IsGoingSomeWhere()) (*i)->SetGoingTo(Pos);
1970 outputfile &operator << (outputfile &SaveFile, const homedata *HomeData) {
1971 if (HomeData) {
1972 SaveFile.Put(1);
1973 SaveFile << HomeData->Pos << HomeData->Dungeon << HomeData->Level << HomeData->Room;
1974 } else SaveFile.Put(0);
1975 return SaveFile;
1979 inputfile &operator >> (inputfile &SaveFile, homedata *&HomeData) {
1980 if (SaveFile.Get()) {
1981 HomeData = new homedata;
1982 SaveFile >> HomeData->Pos >> HomeData->Dungeon >> HomeData->Level >> HomeData->Room;
1984 return SaveFile;
1988 feuLong game::CreateNewCharacterID (character *NewChar) {
1989 feuLong ID = NextCharacterID++;
1990 /*k8:??? if(CharacterIDMap.find(ID) != CharacterIDMap.end())
1991 int esko = esko = 2;*/
1992 CharacterIDMap.insert(std::make_pair(ID, NewChar));
1993 return ID;
1997 feuLong game::CreateNewItemID (item *NewItem) {
1998 feuLong ID = NextItemID++;
1999 /*k8:??? if(ItemIDMap.find(ID) != ItemIDMap.end())
2000 int esko = esko = 2;*/
2001 if (NewItem) ItemIDMap.insert(std::make_pair(ID, NewItem));
2002 return ID;
2006 feuLong game::CreateNewTrapID (entity *NewTrap) {
2007 feuLong ID = NextTrapID++;
2008 /*k8:??? if(TrapIDMap.find(ID) != TrapIDMap.end())
2009 int esko = esko = 2;*/
2010 if (NewTrap) TrapIDMap.insert(std::make_pair(ID, NewTrap));
2011 return ID;
2015 character *game::SearchCharacter (feuLong ID) {
2016 characteridmap::iterator Iterator = CharacterIDMap.find(ID);
2017 return (Iterator != CharacterIDMap.end() ? Iterator->second : 0);
2021 item *game::SearchItem (feuLong ID) {
2022 itemidmap::iterator Iterator = ItemIDMap.find(ID);
2023 return (Iterator != ItemIDMap.end() ? Iterator->second : 0);
2027 entity *game::SearchTrap (feuLong ID) {
2028 trapidmap::iterator Iterator = TrapIDMap.find(ID);
2029 return (Iterator != TrapIDMap.end() ? Iterator->second : 0);
2033 outputfile &operator << (outputfile &SaveFile, const configid &Value) {
2034 SaveFile.Write(reinterpret_cast<cchar*>(&Value), sizeof(Value));
2035 return SaveFile;
2039 inputfile &operator >> (inputfile &SaveFile, configid &Value) {
2040 SaveFile.Read(reinterpret_cast<char*>(&Value), sizeof(Value));
2041 return SaveFile;
2045 outputfile &operator << (outputfile &SaveFile, const dangerid &Value) {
2046 SaveFile << Value.NakedDanger << Value.EquippedDanger;
2047 return SaveFile;
2051 inputfile &operator >> (inputfile &SaveFile, dangerid &Value) {
2052 SaveFile >> Value.NakedDanger >> Value.EquippedDanger;
2053 return SaveFile;
2057 /* The program can only create directories to the deepness of one, no more... */
2058 festring game::GetHomeDir () {
2059 festring Dir;
2060 Dir << getenv("HOME") << '/';
2061 return Dir;
2065 festring game::GetSavePath () {
2066 festring Dir;
2067 #ifdef LOCAL_SAVES
2068 Dir << ivanconfig::GetMyDir() << "/save/";
2069 #else
2070 Dir << getenv("HOME") << "/.ivan-save/";
2071 #endif
2072 return Dir;
2076 festring game::GetGameDir () {
2077 /*k8! return DATADIR "/ivan/"; */
2078 /*k8! return DATADIR "/"; */
2079 festring Dir;
2080 Dir << ivanconfig::GetMyDir() << "/";
2081 return Dir;
2085 festring game::GetBonePath () {
2086 /*k8! return LOCAL_STATE_DIR "/Bones/";*/
2087 festring Dir;
2088 #ifdef LOCAL_SAVES
2089 Dir << ivanconfig::GetMyDir() << "/save/bones/";
2090 #else
2091 Dir << getenv("HOME") << "/.ivan-save/bones/";
2092 #endif
2093 return Dir;
2097 level *game::GetLevel (int I) {
2098 return GetCurrentDungeon()->GetLevel(I);
2102 int game::GetLevels () {
2103 return GetCurrentDungeon()->GetLevels();
2107 void game::SignalDeath (ccharacter *Ghost, ccharacter *Murderer, festring DeathMsg) {
2108 if (InWilderness) DeathMsg << " in the world map";
2109 else DeathMsg << " in " << GetCurrentDungeon()->GetLevelDescription(CurrentLevelIndex);
2110 massacremap *MassacreMap;
2111 if (!Murderer) {
2112 ++MiscMassacreAmount;
2113 MassacreMap = &MiscMassacreMap;
2114 } else if(Murderer->IsPlayer()) {
2115 ++PlayerMassacreAmount;
2116 MassacreMap = &PlayerMassacreMap;
2117 } else if(Murderer->IsPet()) {
2118 ++PetMassacreAmount;
2119 MassacreMap = &PetMassacreMap;
2120 } else {
2121 ++MiscMassacreAmount;
2122 MassacreMap = &MiscMassacreMap;
2125 massacreid MI(Ghost->GetType(), Ghost->GetConfig(), Ghost->GetAssignedName());
2126 massacremap::iterator i = MassacreMap->find(MI);
2128 if (i == MassacreMap->end()) {
2129 i = MassacreMap->insert(std::make_pair(MI, killdata(1, Ghost->GetGenerationDanger()))).first;
2130 i->second.Reason.push_back(killreason(DeathMsg, 1));
2131 } else {
2132 ++i->second.Amount;
2133 i->second.DangerSum += Ghost->GetGenerationDanger();
2134 std::vector<killreason>& Reason = i->second.Reason;
2135 uInt c;
2136 for (c = 0; c < Reason.size(); ++c) {
2137 if (Reason[c].String == DeathMsg) {
2138 ++Reason[c].Amount;
2139 break;
2142 if (c == Reason.size()) Reason.push_back(killreason(DeathMsg, 1));
2147 void game::DisplayMassacreLists () {
2148 DisplayMassacreList(PlayerMassacreMap, "directly by you.", PlayerMassacreAmount);
2149 DisplayMassacreList(PetMassacreMap, "by your allies.", PetMassacreAmount);
2150 DisplayMassacreList(MiscMassacreMap, "by some other reason.", MiscMassacreAmount);
2154 struct massacresetentry {
2155 bool operator < (const massacresetentry &MSE) const { return festring::IgnoreCaseCompare(Key, MSE.Key); }
2156 festring Key;
2157 festring String;
2158 std::vector<festring> Details;
2159 int ImageKey;
2163 void game::DisplayMassacreList (const massacremap &MassacreMap, cchar *Reason, sLong Amount) {
2164 std::set<massacresetentry> MassacreSet;
2165 festring FirstPronoun;
2166 truth First = true;
2167 charactervector GraveYard;
2169 for (massacremap::const_iterator i1 = MassacreMap.begin(); i1 != MassacreMap.end(); ++i1) {
2170 character *Victim = protocontainer<character>::GetProto(i1->first.Type)->Spawn(i1->first.Config);
2171 Victim->SetAssignedName(i1->first.Name);
2172 massacresetentry Entry;
2173 GraveYard.push_back(Victim);
2174 Entry.ImageKey = AddToCharacterDrawVector(Victim);
2175 if (i1->second.Amount == 1) {
2176 Victim->AddName(Entry.Key, UNARTICLED);
2177 Victim->AddName(Entry.String, INDEFINITE);
2178 } else {
2179 Victim->AddName(Entry.Key, PLURAL);
2180 Entry.String << i1->second.Amount << ' ' << Entry.Key;
2182 if (First) {
2183 FirstPronoun = Victim->GetSex() == UNDEFINED ? "it" : Victim->GetSex() == MALE ? "he" : "she";
2184 First = false;
2186 const std::vector<killreason>& Reason = i1->second.Reason;
2187 std::vector<festring>& Details = Entry.Details;
2188 if (Reason.size() == 1) {
2189 festring Begin;
2190 if (Reason[0].Amount == 1) Begin = "";
2191 else if(Reason[0].Amount == 2) Begin = "both ";
2192 else Begin = "all ";
2193 Details.push_back(Begin + Reason[0].String);
2194 } else {
2195 for (uInt c = 0; c < Reason.size(); ++c) Details.push_back(CONST_S("")+Reason[c].Amount+' '+Reason[c].String);
2196 std::sort(Details.begin(), Details.end(), ignorecaseorderer());
2198 MassacreSet.insert(Entry);
2200 sLong Total = PlayerMassacreAmount+PetMassacreAmount+MiscMassacreAmount;
2201 festring MainTopic;
2202 if (Total == 1) MainTopic << "One creature perished during your adventure.";
2203 else MainTopic << Total << " creatures perished during your adventure.";
2204 felist List(MainTopic);
2205 SetStandardListAttributes(List);
2206 List.SetPageLength(15);
2207 List.AddFlags(SELECTABLE);
2208 List.SetEntryDrawer(CharacterEntryDrawer);
2209 List.AddDescription(CONST_S(""));
2210 festring SideTopic;
2211 if (Amount != Total) {
2212 SideTopic = CONST_S("The following ");
2213 if (Amount == 1) SideTopic << "one was killed " << Reason;
2214 else SideTopic << Amount << " were killed " << Reason;
2215 } else {
2216 if (Amount == 1) {
2217 FirstPronoun.Capitalize();
2218 SideTopic << FirstPronoun << " was killed " << Reason;
2219 } else SideTopic << "They were all killed " << Reason;
2221 List.AddDescription(SideTopic);
2222 List.AddDescription(CONST_S(""));
2223 List.AddDescription("Choose a type of creatures to browse death details.");
2224 std::set<massacresetentry>::const_iterator i2;
2225 for (i2 = MassacreSet.begin(); i2 != MassacreSet.end(); ++i2) List.AddEntry(i2->String, LIGHT_GRAY, 0, i2->ImageKey);
2226 for (;;) {
2227 int Chosen = List.Draw();
2228 if (Chosen & FELIST_ERROR_BIT) break;
2229 felist SubList(CONST_S("Massacre details"));
2230 SetStandardListAttributes(SubList);
2231 SubList.SetPageLength(20);
2232 int Counter = 0;
2233 for (i2 = MassacreSet.begin(); i2 != MassacreSet.end(); ++i2, ++Counter) {
2234 if (Counter == Chosen) {
2235 for (uInt c = 0; c < i2->Details.size(); ++c) SubList.AddEntry(i2->Details[c], LIGHT_GRAY);
2236 break;
2239 SubList.Draw();
2241 ClearCharacterDrawVector();
2242 for (uInt c = 0; c < GraveYard.size(); ++c) delete GraveYard[c];
2246 truth game::MassacreListsEmpty () {
2247 return PlayerMassacreMap.empty() && PetMassacreMap.empty() && MiscMassacreMap.empty();
2251 #ifdef WIZARD
2252 void game::SeeWholeMap () {
2253 if (SeeWholeMapCheatMode < 2) ++SeeWholeMapCheatMode; else SeeWholeMapCheatMode = 0;
2254 GetCurrentArea()->SendNewDrawRequest();
2256 #endif
2259 void game::CreateBone () {
2260 if (!WizardModeIsActive() && !IsInWilderness() && (RAND()&3) && GetCurrentLevel()->PreProcessForBone()) {
2261 int BoneIndex;
2262 festring BoneName;
2263 for (BoneIndex = 0; BoneIndex < 1000; ++BoneIndex) {
2264 BoneName = GetBonePath()+"bon_"+CurrentDungeonIndex+"_"+CurrentLevelIndex+"_"+BoneIndex;
2265 if (!inputfile::fileExists(BoneName)) break;
2267 if (BoneIndex != 1000) {
2268 //festring BoneName = GetBonePath()+"bon"+CurrentDungeonIndex+CurrentLevelIndex+BoneIndex;
2269 fprintf(stderr, "creating bone file: [%s]\n", BoneName.CStr());
2270 outputfile BoneFile(BoneName, true);
2271 BoneFile << int(BONE_FILE_VERSION) << PlayerName << CurrentLevel;
2277 truth game::PrepareRandomBone (int LevelIndex) {
2278 if (/*k8:WizardModeIsActive() ||*/ GetCurrentDungeon()->IsGenerated(LevelIndex) || !*GetCurrentDungeon()->GetLevelScript(LevelIndex)->CanGenerateBone()) return false;
2279 int BoneIndex;
2280 festring BoneName;
2281 for (BoneIndex = 0; BoneIndex < 1000; ++BoneIndex) {
2282 BoneName = GetBonePath()+"bon_"+CurrentDungeonIndex+"_"+LevelIndex+"_"+BoneIndex;
2283 inputfile BoneFile(BoneName, false);
2284 if (BoneFile.IsOpen() && !(RAND() & 7)) {
2285 if (ReadType(int, BoneFile) != BONE_FILE_VERSION) {
2286 BoneFile.Close();
2287 remove(BoneName.CStr());
2288 continue;
2290 festring Name;
2291 BoneFile >> Name;
2292 level *NewLevel = GetCurrentDungeon()->LoadLevel(BoneFile, LevelIndex);
2293 if (!NewLevel->PostProcessForBone()) {
2294 delete NewLevel;
2295 GetBoneItemIDMap().clear();
2296 GetBoneCharacterIDMap().clear();
2297 continue;
2299 NewLevel->FinalProcessForBone();
2300 GetBoneItemIDMap().clear();
2301 GetBoneCharacterIDMap().clear();
2302 SetCurrentArea(NewLevel);
2303 CurrentLevel = NewLevel;
2304 CurrentLSquareMap = NewLevel->GetMap();
2305 GetCurrentDungeon()->SetIsGenerated(LevelIndex, true);
2306 if (Name == PlayerName) ADD_MESSAGE("This place is oddly familiar. Like you had been here in one of your past lives.");
2307 else ADD_MESSAGE("You smell the stench of death.");
2308 break;
2311 Generating = true;
2312 if (BoneIndex != 1000) {
2313 remove(BoneName.CStr());
2314 return true;
2316 return false;
2320 double game::CalculateAverageDanger (const charactervector &EnemyVector, character *Char) {
2321 double DangerSum = 0;
2322 int Enemies = 0;
2323 for (uInt c = 0; c < EnemyVector.size(); ++c) {
2324 DangerSum += EnemyVector[c]->GetRelativeDanger(Char, true);
2325 ++Enemies;
2327 return DangerSum/Enemies;
2331 double game::CalculateAverageDangerOfAllNormalEnemies () {
2332 double DangerSum = 0;
2333 int Enemies = 0;
2334 for (int c1 = 1; c1 < protocontainer<character>::GetSize(); ++c1) {
2335 const character::prototype *Proto = protocontainer<character>::GetProto(c1);
2336 const character::database*const *ConfigData = Proto->GetConfigData();
2337 int ConfigSize = Proto->GetConfigSize();
2338 for (int c2 = 0; c2 < ConfigSize; ++c2) {
2339 if (!ConfigData[c2]->IsAbstract && !ConfigData[c2]->IsUnique && ConfigData[c2]->CanBeGenerated) {
2340 DangerSum += DangerMap.find(configid(c1, ConfigData[c2]->Config))->second.EquippedDanger;
2341 ++Enemies;
2345 return DangerSum/Enemies;
2349 character *game::CreateGhost () {
2350 double AverageDanger = CalculateAverageDangerOfAllNormalEnemies();
2351 charactervector EnemyVector;
2352 protosystem::CreateEveryNormalEnemy(EnemyVector);
2353 ghost *Ghost = ghost::Spawn();
2354 Ghost->SetTeam(GetTeam(MONSTER_TEAM));
2355 Ghost->SetGenerationDanger(CurrentLevel->GetDifficulty());
2356 Ghost->SetOwnerSoul(PlayerName);
2357 Ghost->SetIsActive(false);
2358 Ghost->EditAllAttributes(-4);
2359 Player->SetSoulID(Ghost->GetID());
2360 while (CalculateAverageDanger(EnemyVector, Ghost) > AverageDanger && Ghost->EditAllAttributes(1));
2361 for (uInt c = 0; c < EnemyVector.size(); ++c) delete EnemyVector[c];
2362 return Ghost;
2366 int game::GetMoveCommandKey (int I) {
2367 if (!ivanconfig::GetUseAlternativeKeys()) return MoveNormalCommandKey[I];
2368 return MoveAbnormalCommandKey[I];
2372 sLong game::GetScore () {
2373 double Counter = 0;
2374 massacremap::const_iterator i;
2375 massacremap SumMap = PlayerMassacreMap;
2376 for (i = PetMassacreMap.begin(); i != PetMassacreMap.end(); ++i) {
2377 killdata &KillData = SumMap[i->first];
2378 KillData.Amount += i->second.Amount;
2379 KillData.DangerSum += i->second.DangerSum;
2381 for (i = SumMap.begin(); i != SumMap.end(); ++i) {
2382 character *Char = protocontainer<character>::GetProto(i->first.Type)->Spawn(i->first.Config);
2383 int SumOfAttributes = Char->GetSumOfAttributes();
2384 Counter += sqrt(i->second.DangerSum / DEFAULT_GENERATION_DANGER) * SumOfAttributes * SumOfAttributes;
2385 delete Char;
2387 return sLong(0.01*Counter);
2391 /* Only works if New Attnam is loaded */
2392 truth game::TweraifIsFree () {
2393 for (std::list<character*>::const_iterator i = GetTeam(COLONIST_TEAM)->GetMember().begin(); i != GetTeam(COLONIST_TEAM)->GetMember().end(); ++i)
2394 if ((*i)->IsEnabled()) return false;
2395 return true;
2399 // returns true if date is christmaseve or day
2400 truth game::IsXMas () {
2401 time_t Time = time(0);
2402 struct tm *TM = localtime(&Time);
2403 return (TM->tm_mon == 11 && (TM->tm_mday == 24 || TM->tm_mday == 25));
2407 int game::AddToItemDrawVector (const itemvector &What) {
2408 ItemDrawVector.push_back(What);
2409 return ItemDrawVector.size()-1;
2413 v2 ItemDisplacement[3][3] = {
2414 { v2(0, 0), ERROR_V2, ERROR_V2 },
2415 { v2(-2, -2), v2(2, 2), ERROR_V2 },
2416 { v2(-4, -4), v2(0, 0), v2(4, 4) }
2420 void game::ItemEntryDrawer (bitmap *Bitmap, v2 Pos, uInt I) {
2421 blitdata B = {
2422 Bitmap,
2423 { 0, 0 },
2424 { 0, 0 },
2425 { TILE_SIZE, TILE_SIZE },
2426 { NORMAL_LUMINANCE },
2427 TRANSPARENT_COLOR,
2428 ALLOW_ANIMATE
2430 itemvector ItemVector = ItemDrawVector[I];
2431 int Amount = Min<int>(ItemVector.size(), 3);
2432 for (int c = 0; c < Amount; ++c) {
2433 v2 Displacement = ItemDisplacement[Amount-1][c];
2434 if (!ItemVector[0]->HasNormalPictureDirection()) Displacement.X = -Displacement.X;
2435 B.Dest = Pos+Displacement;
2436 if (ItemVector[c]->AllowAlphaEverywhere()) B.CustomData |= ALLOW_ALPHA;
2437 ItemVector[c]->Draw(B);
2438 B.CustomData &= ~ALLOW_ALPHA;
2440 if (ItemVector.size() > 3) {
2441 B.Src.X = 0;
2442 B.Src.Y = 16;
2443 B.Dest = ItemVector[0]->HasNormalPictureDirection() ? Pos+v2(11, -2) : Pos+v2(-2, -2);
2444 B.Flags = 0;
2445 igraph::GetSymbolGraphic()->NormalMaskedBlit(B);
2450 int game::AddToCharacterDrawVector (character *What) {
2451 CharacterDrawVector.push_back(What);
2452 return CharacterDrawVector.size()-1;
2456 void game::CharacterEntryDrawer (bitmap *Bitmap, v2 Pos, uInt I) {
2457 if (CharacterDrawVector[I]) {
2458 blitdata B = {
2459 Bitmap,
2460 { 0, 0 },
2461 { Pos.X, Pos.Y },
2462 { TILE_SIZE, TILE_SIZE },
2463 { NORMAL_LUMINANCE },
2464 TRANSPARENT_COLOR,
2465 ALLOW_ANIMATE|ALLOW_ALPHA
2467 CharacterDrawVector[I]->DrawBodyParts(B);
2472 void game::GodEntryDrawer (bitmap *Bitmap, v2 Pos, uInt I) {
2473 blitdata B = {
2474 Bitmap,
2475 { I << 4, 0 },
2476 { Pos.X, Pos.Y },
2477 { TILE_SIZE, TILE_SIZE },
2478 { 0 },
2479 TRANSPARENT_COLOR,
2482 igraph::GetSymbolGraphic()->NormalMaskedBlit(B);
2486 character *game::GetSumo () {
2487 return GetCurrentLevel()->GetLSquare(SUMO_ROOM_POS)->GetRoom()->GetMaster();
2491 truth game::TryToEnterSumoArena () {
2492 character *Sumo = GetSumo();
2493 if (!Sumo || !Sumo->IsEnabled() || Sumo->GetRelation(Player) == HOSTILE || !Player->CanBeSeenBy(Sumo)) return true;
2494 if (TweraifIsFree()) {
2495 ADD_MESSAGE("\"You started this stupid revolution, after which I've been constantly hungry. Get lost!\"");
2496 return false;
2498 if (PlayerIsSumoChampion()) {
2499 ADD_MESSAGE("\"I don't really enjoy losing, especially many times to the same guy. Go away.\"");
2500 return false;
2502 if (Player->IsPolymorphed()) {
2503 ADD_MESSAGE("\"Don't try to cheat. Come back when you're normal again.\"");
2504 return false;
2506 if (Player->GetHungerState() < SATIATED) {
2507 ADD_MESSAGE("\"Your figure is too slender for this sport. Eat a lot more and come back.\"");
2508 return false;
2510 if (Player->GetHungerState() < BLOATED) {
2511 ADD_MESSAGE("\"You're still somewhat too thin. Eat some more and we'll compete.\"");
2512 return false;
2514 ADD_MESSAGE("\"So you want to compete? Okay, I'll explain the rules. First, I'll make a mirror image out of us both. We'll enter the arena and fight till one is knocked out. Use of any equipment is not allowed. Note that we will not gain experience from fighting as a mirror image, but won't get really hurt, either. However, controlling the image is exhausting and you can get hungry very quickly.\"");
2515 if (!TruthQuestion("Do you want to challenge him? [y/N]")) return false;
2516 pool::AbortBe();
2517 SumoWrestling = true;
2518 character *MirrorPlayer = Player->Duplicate(IGNORE_PROHIBITIONS);
2519 character *MirrorSumo = Sumo->Duplicate(IGNORE_PROHIBITIONS);
2520 SetPlayer(MirrorPlayer);
2521 charactervector Spectators;
2522 if (Player->GetTeam()->GetRelation(GetTeam(TOURIST_GUIDE_TEAM)) != HOSTILE &&
2523 Player->GetTeam()->GetRelation(GetTeam(TOURIST_TEAM)) != HOSTILE) {
2524 GetTeam(TOURIST_GUIDE_TEAM)->MoveMembersTo(Spectators);
2525 GetTeam(TOURIST_TEAM)->MoveMembersTo(Spectators);
2527 GetCurrentDungeon()->SaveLevel(SaveName(), 0);
2528 charactervector test;
2529 EnterArea(test, 1, STAIRS_UP);
2530 MirrorSumo->PutTo(SUMO_ARENA_POS+v2(6, 5));
2531 MirrorSumo->ChangeTeam(GetTeam(SUMO_TEAM));
2532 GetCurrentLevel()->GetLSquare(SUMO_ARENA_POS)->GetRoom()->SetMasterID(MirrorSumo->GetID());
2533 for (uInt c = 0; c < Spectators.size(); ++c) Spectators[c]->PutToOrNear(SUMO_ARENA_POS + v2(6, 10));
2534 throw areachangerequest();
2535 return true;
2539 truth game::TryToExitSumoArena () {
2540 if (GetTeam(PLAYER_TEAM)->GetRelation(GetTeam(NEW_ATTNAM_TEAM)) == HOSTILE) return true;
2541 itemvector IVector;
2542 charactervector CVector;
2543 if (IsSumoWrestling()) {
2544 if (TruthQuestion("Do you really wish to give up? [y/N]")) return EndSumoWrestling(LOST);
2545 return false;
2546 } else {
2547 pool::AbortBe();
2548 Player->Remove();
2549 GetCurrentLevel()->CollectEverything(IVector, CVector);
2550 GetCurrentDungeon()->SaveLevel(SaveName(), 1);
2551 std::vector<character*> test;
2552 EnterArea(test, 0, STAIRS_DOWN);
2553 Player->GetStackUnder()->AddItems(IVector);
2554 if (!IVector.empty()) {
2555 character *Sumo = GetSumo();
2556 if (Sumo && Sumo->GetRelation(Player) != HOSTILE && Player->CanBeSeenBy(Sumo)) ADD_MESSAGE("\"Don't leave anything there, please.\"");
2558 v2 PlayerPos = Player->GetPos();
2559 for (uInt c = 0; c < CVector.size(); ++c) CVector[c]->PutNear(PlayerPos);
2560 throw areachangerequest();
2561 return true;
2566 truth game::EndSumoWrestling (int Result) {
2567 pool::AbortBe();
2568 msgsystem::LeaveBigMessageMode();
2569 if (Result == LOST) AskForKeyPress("You lose. [press any key to continue]");
2570 else if (Result == WON) AskForKeyPress("You win! [press any key to continue]");
2571 else if (Result == DISQUALIFIED) AskForKeyPress("You are disqualified! [press any key to continue]");
2572 character *Sumo = GetCurrentLevel()->GetLSquare(SUMO_ARENA_POS)->GetRoom()->GetMaster();
2573 /* We'll make a throw soon so deletes are allowed */
2574 if (Sumo) {
2575 Sumo->Remove();
2576 delete Sumo;
2578 Player->Remove();
2579 delete Player;
2580 SetPlayer(0);
2581 itemvector IVector;
2582 charactervector CVector;
2583 GetCurrentLevel()->CollectEverything(IVector, CVector);
2584 GetCurrentDungeon()->SaveLevel(SaveName(), 1);
2585 charactervector test;
2586 EnterArea(test, 0, STAIRS_DOWN);
2587 SumoWrestling = false;
2588 Player->GetStackUnder()->AddItems(IVector);
2589 v2 PlayerPos = Player->GetPos();
2590 for (uInt c = 0; c < CVector.size(); ++c) CVector[c]->PutNear(PlayerPos);
2591 if (Result == LOST) ADD_MESSAGE("\"I hope you've learned your lesson now!\"");
2592 else if (Result == DISQUALIFIED) ADD_MESSAGE("\"Don't do that again or I'll be really angry!\"");
2593 else {
2594 PlayerSumoChampion = true;
2595 character *Sumo = GetSumo();
2596 festring Msg = Sumo->GetName(DEFINITE)+" seems humbler than before. \"Darn. You bested me.\n";
2597 Msg << "Here's a little something as a reward\", " << Sumo->GetPersonalPronoun() << " says and hands you a belt of levitation.\n\"";
2598 (belt::Spawn(BELT_OF_LEVITATION))->MoveTo(Player->GetStack());
2599 Msg << "Allow me to also teach you a few nasty martial art tricks the years have taught me.\"";
2600 Player->GetCWeaponSkill(UNARMED)->AddHit(100000);
2601 Player->GetCWeaponSkill(KICK)->AddHit(100000);
2602 character *Imperialist = GetCurrentLevel()->GetLSquare(5, 5)->GetRoom()->GetMaster();
2603 if (Imperialist && Imperialist->GetRelation(Player) != HOSTILE) {
2604 v2 Pos = Player->GetPos()+v2(0, 1);
2605 GetCurrentLevel()->GetLSquare(Pos)->KickAnyoneStandingHereAway();
2606 Imperialist->Remove();
2607 Imperialist->PutTo(Pos);
2608 Msg << "\n\nSuddenly you notice " << Imperialist->GetName(DEFINITE) << " has also entered.\n"
2609 "\"I see we have a promising fighter among us. I had already heard of your\n"
2610 "adventures outside the village, but hardly could I believe that one day you\n"
2611 "would defeat even the mighty Huang Ming Pong! A hero such as you is bound\n"
2612 "to become world famous, and can earn a fortune if wealthy sponsors are behind\n"
2613 "him. May I therefore propose a mutually profitable contract: I'll give you this\n"
2614 "nice shirt with my company's ad, and you'll wear it as you journey bravely to\n"
2615 "the unknown and fight epic battles against the limitless minions of evil. I'll\n"
2616 "reward you well when you return, depending on how much you have used it.\"";
2617 Player->GetStack()->AddItem(decosadshirt::Spawn());
2619 TextScreen(Msg);
2620 GetCurrentArea()->SendNewDrawRequest();
2621 DrawEverything();
2623 Player->EditNP(-25000);
2624 Player->CheckStarvationDeath(CONST_S("exhausted after controlling a mirror image for too long"));
2625 throw areachangerequest();
2626 return true;
2630 rain *game::ConstructGlobalRain () {
2631 return new rain(GlobalRainLiquid, static_cast<lsquare*>(GetSquareInLoad()), GlobalRainSpeed, MONSTER_TEAM, false);
2635 v2 game::GetSunLightDirectionVector () {
2636 int Index = Tick % 48000 / 1000;
2637 /* Should have the same sign as sin(PI * Index / 24) and XTable[Index] /
2638 YTable[Index] should equal roughly -tan(PI * Index / 24). Also, vector
2639 (XTable[Index], YTable[Index]) + P should not be a valid position of
2640 any possible level L for any P belonging to L. */
2641 static int XTable[48] = {
2642 0, 1000, 1000, 1000, 1000, 1000,
2643 1000, 1303, 1732, 2414, 3732, 7596,
2644 1000, 7596, 3732, 2414, 1732, 1303,
2645 1000, 1000, 1000, 1000, 1000, 1000,
2646 0, -1000, -1000, -1000, -1000, -1000,
2647 -1000, -1303, -1732, -2414, -3732, -7596,
2648 -1000, -7596, -3732, -2414, -1732, -1303,
2649 -1000, -1000, -1000, -1000, -1000, -1000 };
2650 /* Should have the same sign as -cos(PI * Index / 24) */
2651 static int YTable[48] = { -1000, -7596, -3732, -2414, -1732, -1303,
2652 -1000, -1000, -1000, -1000, -1000, -1000,
2653 0, 1000, 1000, 1000, 1000, 1000,
2654 1000, 1303, 1732, 2414, 3732, 7596,
2655 1000, 7596, 3732, 2414, 1732, 1303,
2656 1000, 1000, 1000, 1000, 1000, 1000,
2657 0, -1000, -1000, -1000, -1000, -1000,
2658 -1000, -1303, -1732, -2414, -3732, -7596 };
2659 return v2(XTable[Index], YTable[Index]);
2663 int game::CalculateMinimumEmitationRadius (col24 E) {
2664 int MaxElement = Max(GetRed24(E), GetGreen24(E), GetBlue24(E));
2665 return int(sqrt(double(MaxElement << 7) / LIGHT_BORDER - 120.));
2669 feuLong game::IncreaseSquarePartEmitationTicks () {
2670 if ((SquarePartEmitationTick += 2) == 0x100) {
2671 CurrentLevel->InitSquarePartEmitationTicks();
2672 SquarePartEmitationTick = 2;
2674 return SquarePartEmitationTick;
2678 bool game::Wish (character *Wisher, cchar *MsgSingle, cchar *MsgPair, bool canAbort) {
2679 for (;;) {
2680 festring oldDef = DefaultWish;
2681 festring Temp = DefaultQuestion(CONST_S("What do you want to wish for?"), DefaultWish);
2682 if (DefaultWish == "nothing" && canAbort) {
2683 DefaultWish = oldDef;
2684 return false;
2686 if (Temp == "socm") Temp = "scroll of change material";
2687 else if (Temp == "soc") Temp = "scroll of charging";
2688 else if (Temp == "sodm") Temp = "scroll of detect material";
2689 else if (Temp == "soea") Temp = "scroll of enchant armor";
2690 else if (Temp == "soew") Temp = "scroll of enchant weapon";
2691 else if (Temp == "sof") Temp = "scroll of fireballs";
2692 else if (Temp == "sogc") Temp = "scroll of golem creation";
2693 else if (Temp == "sohm") Temp = "scroll of harden material";
2694 else if (Temp == "sor") Temp = "scroll of repair";
2695 else if (Temp == "sot") Temp = "scroll of taming";
2696 else if (Temp == "sotp") Temp = "scroll of teleportation";
2697 else if (Temp == "sow") Temp = "scroll of wishing";
2698 else if (Temp == "vodka") Temp = "bottle full of vodka";
2699 else if (Temp == "troll blood") Temp = "bottle full of troll blood";
2700 item *TempItem = protosystem::CreateItem(Temp, Wisher->IsPlayer());
2701 if (TempItem) {
2702 Wisher->GetStack()->AddItem(TempItem);
2703 TempItem->SpecialGenerationHandler();
2704 if (TempItem->HandleInPairs()) ADD_MESSAGE(MsgPair, TempItem->CHAR_NAME(PLURAL));
2705 else ADD_MESSAGE(MsgSingle, TempItem->CHAR_NAME(INDEFINITE));
2706 return true;
2712 festring game::DefaultQuestion (festring Topic, festring &Default, stringkeyhandler KeyHandler) {
2713 festring ShortDefault = Default;
2714 if (Default.GetSize() > 29) {
2715 ShortDefault.Resize(27);
2716 ShortDefault = ShortDefault << CONST_S("...");
2718 if (!Default.IsEmpty()) Topic << " [" << ShortDefault << ']';
2719 festring Answer = StringQuestion(Topic, WHITE, 0, 80, false, KeyHandler);
2720 if (Answer.IsEmpty()) Answer = Default;
2721 return Default = Answer;
2725 void game::GetTime (ivantime &Time) {
2726 Time.Hour = 12 + Tick / 2000;
2727 Time.Day = Time.Hour / 24 + 1;
2728 Time.Hour %= 24;
2729 Time.Min = Tick % 2000 * 60 / 2000;
2733 truth NameOrderer (character *C1, character *C2) {
2734 return festring::IgnoreCaseCompare(C1->GetName(UNARTICLED), C2->GetName(UNARTICLED));
2738 truth game::PolymorphControlKeyHandler (int Key, festring &String) {
2739 if (Key == '?') {
2740 felist List(CONST_S("List of known creatures and their intelligence requirements"));
2741 SetStandardListAttributes(List);
2742 List.SetPageLength(15);
2743 List.AddFlags(SELECTABLE);
2744 protosystem::CreateEverySeenCharacter(CharacterDrawVector);
2745 std::sort(CharacterDrawVector.begin(), CharacterDrawVector.end(), NameOrderer);
2746 List.SetEntryDrawer(CharacterEntryDrawer);
2747 std::vector<festring> StringVector;
2748 uInt c;
2749 for (c = 0; c < CharacterDrawVector.size(); ++c) {
2750 character *Char = CharacterDrawVector[c];
2751 if (Char->CanBeWished()) {
2752 festring Entry;
2753 Char->AddName(Entry, UNARTICLED);
2754 StringVector.push_back(Entry);
2755 int Req = Char->GetPolymorphIntelligenceRequirement();
2756 if (Char->IsSameAs(Player) || (Player->GetPolymorphBackup() && Player->GetPolymorphBackup()->IsSameAs(Char))) Req = 0;
2757 Entry << " (" << Req << ')';
2758 int Int = Player->GetAttribute(INTELLIGENCE);
2759 List.AddEntry(Entry, Req > Int ? RED : LIGHT_GRAY, 0, c);
2762 int Chosen = List.Draw();
2763 for (c = 0; c < CharacterDrawVector.size(); ++c) delete CharacterDrawVector[c];
2764 if (!(Chosen & FELIST_ERROR_BIT)) String = StringVector[Chosen];
2765 CharacterDrawVector.clear();
2766 return true;
2768 return false;
2772 outputfile &operator << (outputfile &SaveFile, const killdata &Value) {
2773 SaveFile << Value.Amount << Value.DangerSum << Value.Reason;
2774 return SaveFile;
2778 inputfile &operator >> (inputfile &SaveFile, killdata &Value) {
2779 SaveFile >> Value.Amount >> Value.DangerSum >> Value.Reason;
2780 return SaveFile;
2784 outputfile &operator << (outputfile &SaveFile, const killreason &Value) {
2785 SaveFile << Value.Amount << Value.String;
2786 return SaveFile;
2790 inputfile &operator >> (inputfile &SaveFile, killreason &Value) {
2791 SaveFile >> Value.Amount >> Value.String;
2792 return SaveFile;
2796 truth DistanceOrderer (character *C1, character *C2) {
2797 v2 PlayerPos = PLAYER->GetPos();
2798 v2 Pos1 = C1->GetPos();
2799 v2 Pos2 = C2->GetPos();
2800 int D1 = Max(abs(Pos1.X - PlayerPos.X), abs(Pos1.Y - PlayerPos.Y));
2801 int D2 = Max(abs(Pos2.X - PlayerPos.X), abs(Pos2.Y - PlayerPos.Y));
2802 if (D1 != D2) return D1 < D2;
2803 if (Pos1.Y != Pos2.Y) return Pos1.Y < Pos2.Y;
2804 return Pos1.X < Pos2.X;
2808 truth game::FillPetVector (cchar *Verb) {
2809 PetVector.clear();
2810 team *Team = GetTeam(PLAYER_TEAM);
2811 for (std::list<character*>::const_iterator i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i)
2812 if ((*i)->IsEnabled() && !(*i)->IsPlayer() && (*i)->CanBeSeenByPlayer()) PetVector.push_back(*i);
2813 if (PetVector.empty()) {
2814 ADD_MESSAGE("You don't detect any friends to %s.", Verb);
2815 return false;
2817 std::sort(PetVector.begin(), PetVector.end(), DistanceOrderer);
2818 LastPetUnderCursor = PetVector[0];
2819 return true;
2823 truth game::CommandQuestion () {
2824 if (!FillPetVector("command")) return false;
2825 character *Char;
2826 if (PetVector.size() == 1) Char = PetVector[0];
2827 else {
2828 v2 Pos = PetVector[0]->GetPos();
2829 Pos = PositionQuestion(CONST_S("Whom do you wish to command? [direction keys/'+'/'-'/'a'll/space/esc]"), Pos, &PetHandler, &CommandKeyHandler);
2830 if (Pos == ERROR_V2) return false;
2831 if (Pos == ABORT_V2) return true;
2832 Char = CurrentArea->GetSquare(Pos)->GetCharacter();
2833 if (!Char || !Char->CanBeSeenByPlayer()) {
2834 ADD_MESSAGE("You don't see anyone here to command.");
2835 return false;
2837 if (Char->IsPlayer()) {
2838 ADD_MESSAGE("You do that all the time.");
2839 return false;
2841 if (!Char->IsPet()) {
2842 ADD_MESSAGE("%s refuses to be commanded by you.", Char->CHAR_NAME(DEFINITE));
2843 return false;
2846 return Char->IssuePetCommands();
2850 void game::NameQuestion () {
2851 if (!FillPetVector("name")) return;
2852 if (PetVector.size() == 1) PetVector[0]->TryToName();
2853 else PositionQuestion(CONST_S("Who do you want to name? [direction keys/'+'/'-'/'n'ame/esc]"), PetVector[0]->GetPos(), &PetHandler, &NameKeyHandler);
2857 void game::PetHandler (v2 CursorPos) {
2858 character *Char = CurrentArea->GetSquare(CursorPos)->GetCharacter();
2859 if (Char && Char->CanBeSeenByPlayer() && Char->IsPet() && !Char->IsPlayer()) CursorData = RED_CURSOR|CURSOR_TARGET;
2860 else CursorData = RED_CURSOR;
2861 if (Char && !Char->IsPlayer() && Char->IsPet()) LastPetUnderCursor = Char;
2865 v2 game::CommandKeyHandler (v2 CursorPos, int Key) {
2866 if (SelectPet(Key)) return LastPetUnderCursor->GetPos();
2867 if (Key == 'a' || Key == 'A') return CommandAll() ? ABORT_V2 : ERROR_V2;
2868 return CursorPos;
2872 truth game::SelectPet (int Key) {
2873 if (Key == '+') {
2874 for (uInt c = 0; c < PetVector.size(); ++c) {
2875 if (PetVector[c] == LastPetUnderCursor) {
2876 if (++c == PetVector.size()) c = 0;
2877 LastPetUnderCursor = PetVector[c];
2878 return true;
2881 } else if (Key == '-') {
2882 for (uInt c = 0; c < PetVector.size(); ++c) {
2883 if (PetVector[c] == LastPetUnderCursor) {
2884 if (!c) c = PetVector.size();
2885 LastPetUnderCursor = PetVector[--c];
2886 return true;
2890 return false;
2894 void game::CommandScreen (cfestring &Topic, feuLong PossibleFlags, feuLong ConstantFlags, feuLong &VaryFlags, feuLong &Flags) {
2895 static cchar *CommandDescription[COMMAND_FLAGS] = {
2896 "Follow me",
2897 "Flee from enemies",
2898 "Don't change your equipment",
2899 "Don't consume anything valuable"
2901 felist List(Topic);
2902 SetStandardListAttributes(List);
2903 List.AddFlags(SELECTABLE);
2904 List.AddDescription(CONST_S(""));
2905 List.AddDescription(CONST_S("Command Active?"));
2906 for (;;) {
2907 int c, i;
2908 for (c = 0; c < COMMAND_FLAGS; ++c) {
2909 if (1 << c & PossibleFlags) {
2910 truth Changeable = !(1 << c & ConstantFlags);
2911 festring Entry;
2912 if (Changeable) {
2913 Entry = CommandDescription[c];
2914 Entry.Resize(60);
2915 } else {
2916 Entry << " " << CommandDescription[c];
2917 Entry.Resize(63);
2919 if (1 << c & VaryFlags) Entry << "varies"; else Entry << (1 << c & Flags ? "yes" : "no");
2920 List.AddEntry(Entry, Changeable ? LIGHT_GRAY : DARK_GRAY, 0, NO_IMAGE, Changeable);
2923 int Chosen = List.Draw();
2924 if (Chosen & FELIST_ERROR_BIT) return;
2925 for (c = 0, i = 0; c < COMMAND_FLAGS; ++c) {
2926 if (1 << c & PossibleFlags && !(1 << c & ConstantFlags) && i++ == Chosen) {
2927 if (1 << c & VaryFlags) {
2928 VaryFlags &= ~(1 << c);
2929 Flags |= 1 << c;
2930 } else Flags ^= 1 << c;
2931 break;
2934 List.Empty();
2935 DrawEverythingNoBlit();
2940 truth game::CommandAll () {
2941 feuLong PossibleFlags = 0, ConstantFlags = ALL_COMMAND_FLAGS, VaryFlags = 0, OldFlags = 0;
2942 uInt c1, c2;
2943 for (c1 = 0; c1 < PetVector.size(); ++c1) {
2944 ConstantFlags &= PetVector[c1]->GetConstantCommandFlags();
2945 feuLong C = PetVector[c1]->GetCommandFlags();
2946 feuLong ThisPossible = PetVector[c1]->GetPossibleCommandFlags();
2947 for (c2 = 0; c2 < COMMAND_FLAGS; ++c2)
2948 if (1 << c2 & PossibleFlags & ThisPossible && (1 << c2 & C) != (1 << c2 & OldFlags)) VaryFlags |= 1 << c2;
2949 PossibleFlags |= ThisPossible;
2950 OldFlags |= C & ThisPossible;
2952 if (!PossibleFlags) {
2953 ADD_MESSAGE("Not a single creature in your visible team can be commanded.");
2954 return false;
2956 feuLong NewFlags = OldFlags;
2957 CommandScreen(CONST_S("Issue commands to whole visible team"), PossibleFlags, ConstantFlags, VaryFlags, NewFlags);
2958 truth Change = false;
2959 for (c1 = 0; c1 < PetVector.size(); ++c1) {
2960 character *Char = PetVector[c1];
2961 if (!Char->IsConscious()) continue;
2962 feuLong OldC = Char->GetCommandFlags();
2963 feuLong ConstC = Char->GetConstantCommandFlags();
2964 feuLong ThisC = (NewFlags & Char->GetPossibleCommandFlags() & ~(ConstC|VaryFlags)) | (OldC & (ConstC|VaryFlags));
2965 if (ThisC != OldC) Change = true;
2966 Char->SetCommandFlags(ThisC);
2968 if (!Change) return false;
2969 Player->EditAP(-500);
2970 Player->EditExperience(CHARISMA, 50, 1 << 7);
2971 return true;
2975 col16 game::GetAttributeColor (int I) {
2976 int Delta = GetTick()-LastAttributeChangeTick[I];
2977 if (OldAttribute[I] == NewAttribute[I] || Delta >= 510) return WHITE;
2978 if (OldAttribute[I] < NewAttribute[I]) return MakeRGB16(255, 255, Delta >> 1);
2979 return MakeRGB16(255, Delta >> 1, Delta >> 1);
2983 void game::UpdateAttributeMemory () {
2984 for (int c = 0; c < ATTRIBUTES; ++c) {
2985 int A = Player->GetAttribute(c);
2986 if (A != NewAttribute[c]) {
2987 OldAttribute[c] = NewAttribute[c];
2988 NewAttribute[c] = A;
2989 LastAttributeChangeTick[c] = GetTick();
2995 void game::InitAttributeMemory () {
2996 for (int c = 0; c < ATTRIBUTES; ++c) OldAttribute[c] = NewAttribute[c] = Player->GetAttribute(c);
3000 void game::TeleportHandler (v2 CursorPos) {
3001 if ((CursorPos-Player->GetPos()).GetLengthSquare() > Player->GetTeleportRangeSquare())
3002 CursorData = BLUE_CURSOR|CURSOR_TARGET;
3003 else
3004 CursorData = RED_CURSOR|CURSOR_TARGET;
3008 double game::GetGameSituationDanger () {
3009 double SituationDanger = 0;
3010 character *Player = GetPlayer();
3011 truth PlayerStuck = Player->IsStuck();
3012 v2 PlayerPos = Player->GetPos();
3013 character *TruePlayer = Player;
3014 if (PlayerStuck) (Player = Player->Duplicate(IGNORE_PROHIBITIONS))->ChangeTeam(0);
3015 for (int c1 = 0; c1 < GetTeams(); ++c1)
3016 if (GetTeam(c1)->GetRelation(GetTeam(PLAYER_TEAM)) == HOSTILE)
3017 for (std::list<character*>::const_iterator i1 = GetTeam(c1)->GetMember().begin(); i1 != GetTeam(c1)->GetMember().end(); ++i1) {
3018 character *Enemy = *i1;
3019 if (Enemy->IsEnabled() && Enemy->CanAttack() && (Enemy->CanMove() || Enemy->GetPos().IsAdjacent(PlayerPos))) {
3020 truth EnemyStuck = Enemy->IsStuck();
3021 v2 EnemyPos = Enemy->GetPos();
3022 truth Sees = TruePlayer->CanBeSeenBy(Enemy);
3023 character *TrueEnemy = Enemy;
3024 if (EnemyStuck) Enemy = Enemy->Duplicate(IGNORE_PROHIBITIONS);
3025 double PlayerTeamDanger = 1/Enemy->GetSituationDanger(Player, EnemyPos, PlayerPos, Sees);
3026 for (int c2 = 0; c2 < GetTeams(); ++c2)
3027 if (GetTeam(c2)->GetRelation(GetTeam(c1)) == HOSTILE)
3028 for (std::list<character*>::const_iterator i2 = GetTeam(c2)->GetMember().begin(); i2 != GetTeam(c2)->GetMember().end(); ++i2) {
3029 character *Friend = *i2;
3030 if (Friend->IsEnabled() && !Friend->IsPlayer() && Friend->CanAttack() && (Friend->CanMove() || Friend->GetPos().IsAdjacent(EnemyPos))) {
3031 v2 FriendPos = Friend->GetPos();
3032 truth Sees = TrueEnemy->CanBeSeenBy(Friend);
3033 if (Friend->IsStuck()) {
3034 Friend = Friend->Duplicate(IGNORE_PROHIBITIONS);
3035 PlayerTeamDanger += Friend->GetSituationDanger(Enemy, FriendPos, EnemyPos, Sees) * .2;
3036 delete Friend;
3037 } else PlayerTeamDanger += Friend->GetSituationDanger(Enemy, FriendPos, EnemyPos, Sees);
3040 if (EnemyStuck) {
3041 PlayerTeamDanger *= 5;
3042 delete Enemy;
3044 SituationDanger += 1 / PlayerTeamDanger;
3047 Player->ModifySituationDanger(SituationDanger);
3048 if (PlayerStuck) {
3049 SituationDanger *= 2;
3050 delete Player;
3052 return SituationDanger;
3056 sLong game::GetTimeSpent () {
3057 return time::TimeAdd(time::TimeDifference(time(0),LastLoad), TimePlayedBeforeLastLoad);
3061 outputfile &operator << (outputfile &SaveFile, const massacreid &MI) {
3062 SaveFile << MI.Type << MI.Config << MI.Name;
3063 return SaveFile;
3067 inputfile &operator >> (inputfile &SaveFile, massacreid &MI) {
3068 SaveFile >> MI.Type >> MI.Config >> MI.Name;
3069 return SaveFile;
3073 truth game::PlayerIsRunning () {
3074 return PlayerRunning && Player->CanMove();
3078 void game::AddSpecialCursor (v2 Pos, int Data) {
3079 SpecialCursorPos.push_back(Pos);
3080 SpecialCursorData.push_back(Data);
3084 void game::RemoveSpecialCursors () {
3085 SpecialCursorPos.clear();
3086 SpecialCursorData.clear();
3090 void game::LearnAbout (god *Who) {
3091 Who->SetIsKnown(true);
3092 /* slightly slow, but doesn't matter since this is run so rarely */
3093 if (PlayerKnowsAllGods() && !game::PlayerHasReceivedAllGodsKnownBonus) {
3094 GetPlayer()->ApplyAllGodsKnownBonus();
3095 game::PlayerHasReceivedAllGodsKnownBonus = true;
3100 truth game::PlayerKnowsAllGods () {
3101 for (int c = 1; c <= GODS; ++c) if (!GetGod(c)->IsKnown()) return false;
3102 return true;
3106 void game::AdjustRelationsToAllGods (int Amount) {
3107 for (int c = 1; c <= GODS; ++c) GetGod(c)->AdjustRelation(Amount);
3111 void game::SetRelationsToAllGods (int Amount) {
3112 for (int c = 1; c <= GODS; ++c) GetGod(c)->SetRelation(Amount);
3116 void game::ShowDeathSmiley (bitmap *Buffer, truth) {
3117 static blitdata B = {
3119 { 0, 0 },
3120 { (RES.X >> 1) - 24, RES.Y * 4 / 7 - 24 },
3121 { 48, 48 },
3122 { 0 },
3123 TRANSPARENT_COLOR,
3126 int Tick = globalwindowhandler::UpdateTick();
3127 if (((Tick >> 1) & 31) == 1) B.Src.X = 48;
3128 else if (((Tick >> 1) & 31) == 2) B.Src.X = 96;
3129 else B.Src.X = 0;
3130 B.Bitmap = Buffer;
3131 igraph::GetSmileyGraphic()->NormalBlit(B);
3132 if (Buffer == DOUBLE_BUFFER) graphics::BlitDBToScreen();
3136 static int doListSelector (felist &list, int defsel, int cnt) {
3137 game::SetStandardListAttributes(list);
3138 list.AddFlags(SELECTABLE | FELIST_NO_BADKEY_EXIT);
3139 if (defsel > 0) list.SetSelected(defsel);
3140 uInt sel = list.Draw();
3141 list.Empty();
3142 list.RemoveFlags(SELECTABLE | FELIST_NO_BADKEY_EXIT);
3143 if (sel & FELIST_ERROR_BIT) return -1;
3144 if (sel >= (uInt)cnt) return -1;
3145 return (int)sel;
3149 int game::ListSelector (int defsel, const cfestring title, ...) {
3150 int cnt = 0;
3151 va_list items;
3152 va_start(items, title);
3154 felist list(title);
3155 for (;;) {
3156 const char *s = va_arg(items, const char *);
3157 if (!s) break;
3158 list.AddEntry(s, LIGHT_GRAY);
3159 cnt++;
3161 va_end(items);
3162 return doListSelector(list, defsel, cnt);
3166 int game::ListSelectorArray (int defsel, cfestring &title, const char *items[]) {
3167 int cnt = 0;
3168 felist list(title);
3169 for (;;) {
3170 if (!items[cnt]) break;
3171 list.AddEntry(items[cnt], LIGHT_GRAY);
3172 cnt++;
3174 return doListSelector(list, defsel, cnt);
3178 void game::ClearEventData () {
3179 mChar = 0;
3180 mActor = 0;
3181 mSecondActor = 0;
3182 mItem = 0;
3186 // '.': string or number
3187 // 'n': number
3188 // 's': string
3189 // '*': collect all args
3190 int game::ParseFuncArgs (cfestring &types, std::vector<FuncArg> &args, TextInput *fl, truth noterm) {
3191 festring s;
3192 sLong n;
3193 truth isStr;
3194 if (!fl) fl = mFEStack.top();
3195 args.clear();
3196 for (unsigned int f = 0; f < types.GetSize(); f++) {
3197 switch (types[f]) {
3198 case '.':
3199 s = fl->ReadStringOrNumber(&n, &isStr, true);
3200 if (isStr) args.push_back(FuncArg(s)); else args.push_back(FuncArg(n));
3201 break;
3202 case 'n':
3203 n = fl->ReadNumber(0xFF, true);
3204 args.push_back(FuncArg(n));
3205 break;
3206 case '*':
3207 for (;;) {
3208 s = fl->ReadStringOrNumber(&n, &isStr, true);
3209 if (isStr) args.push_back(FuncArg(s)); else args.push_back(FuncArg(n));
3210 fl->ReadWord(s, true);
3211 if (s == ";") return args.size();
3212 if (s != ",") ABORT("',' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3214 // never reached
3215 case 's':
3216 default:
3217 s = fl->ReadWord(true);
3218 args.push_back(FuncArg(s));
3219 break;
3221 if (f == types.GetSize()-1) {
3222 if (noterm) break;
3223 fl->ReadWord(s, true);
3224 if (s != ";") ABORT("';' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3225 break;
3226 } else {
3227 fl->ReadWord(s, true);
3228 if (s != ",") ABORT("',' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3231 return args.size();
3235 truth game::GetWord (festring &w) {
3236 for (;;) {
3237 TextInput *fl = mFEStack.top();
3238 fl->ReadWord(w, false);
3239 if (w == "" && fl->Eof()) {
3240 delete fl;
3241 mFEStack.pop();
3242 if (mFEStack.empty()) return false;
3243 continue;
3245 if (w == "Include") {
3246 fl->ReadWord(w, true);
3247 if (fl->ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3248 w = game::GetGameDir()+"script/"+w;
3249 TextInput *fl = new TextInputFile(w, &game::GetGlobalValueMap(), true);
3250 fl->setGetVarCB(game::ldrGetVar);
3251 mFEStack.push(fl);
3252 continue;
3254 if (w == "Message") {
3255 fl->ReadWord(w, true);
3256 if (fl->ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3257 fprintf(stderr, "MESSAGE: %s\n", w.CStr());
3258 continue;
3260 return true;
3265 void game::SkipBlock (truth brcEaten) {
3266 festring w;
3267 if (!brcEaten) {
3268 mFEStack.top()->ReadWord(w, true);
3269 if (w != "{") ABORT("'{' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3271 int cnt = 1;
3272 for (;;) {
3273 mFEStack.top()->ReadWord(w, true);
3274 if (w == "{") cnt++;
3275 else if (w == "}") {
3276 if (--cnt < 1) break;
3282 truth game::DoOnEvent (truth brcEaten, truth AllowScript) {
3283 // do; only funcalls for now
3284 truth eaten = AllowScript ? true : false;
3285 festring w;
3286 if (!brcEaten) {
3287 mFEStack.top()->ReadWord(w, true);
3288 if (w != "{") ABORT("'{' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3290 for (;;) {
3291 if (!GetWord(w)) {
3292 if (AllowScript) break;
3293 ABORT("Unexpected end of file %s!", mFEStack.top()->GetFileName().CStr());
3295 //fprintf(stderr, " :[%s]\n", w.CStr());
3296 if (w == "}") {
3297 if (AllowScript) ABORT("Unexpected '}' in AllowScript file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3298 break;
3300 if (w == ";") continue;
3301 if (w == "@") {
3302 mFEStack.top()->ReadWord(w, true);
3303 if (mFEStack.top()->ReadWord(true) != "=") ABORT("'=' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3304 //fprintf(stderr, "setvar: %s\n", w.CStr());
3305 if (w == "money") {
3306 sLong n = mFEStack.top()->ReadNumber(true);
3307 if (n < 0) n = 0;
3308 if (mChar) mChar->SetMoney(n);
3309 continue;
3311 if (w == "result") {
3312 mResult = mFEStack.top()->ReadNumber(true);
3313 continue;
3315 ABORT("Unknown var [%s] in file %s at line %d!", w.CStr(), mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3316 } else {
3317 //mFEStack.top()->ReadWord(w, true);
3318 std::vector<FuncArg> args;
3319 //fprintf(stderr, "funcall: %s\n", w.CStr());
3321 if (w == "AddItem") {
3322 ParseFuncArgs("s", args);
3323 item *it = protosystem::CreateItem(args[0].sval, false); // no output
3324 if (it) {
3325 mChar->GetStack()->AddItem(it);
3326 it->SpecialGenerationHandler();
3327 } else {
3328 ADD_MESSAGE("ERROR: no item with id \"%s\"", args[0].sval.CStr());
3330 continue;
3333 if (w == "ActivateTombOfXinroch") {
3334 //GetArea()->SendNewDrawRequest();
3335 //game::ActivateWizardMode();
3336 //ADD_MESSAGE("Wizard mode activated.");
3338 //game::SetXinrochTombStoryState(1);
3339 //game::SaveWorldMap();
3341 maptotombofxinroch *MapToTombOfXinroch = maptotombofxinroch::Spawn();
3342 MapToTombOfXinroch->MoveTo(PLAYER->GetStack());
3343 //MapToTombOfXinroch->FinishReading(PLAYER);
3346 if (!game::IsInWilderness()) game::LoadWorldMap();
3347 v2 XinrochTombPos = game::GetWorldMap()->GetEntryPos(0, XINROCH_TOMB);
3348 game::GetWorldMap()->GetWSquare(XinrochTombPos)->ChangeOWTerrain(xinrochtomb::Spawn());
3349 game::GetWorldMap()->RevealEnvironment(XinrochTombPos, 1);
3351 //game::SaveWorldMap();
3353 (belt::Spawn(BELT_OF_LEVITATION))->MoveTo(Player->GetStack());
3356 if (!game::IsInWilderness()) game::LoadWorldMap();
3357 v2 ElpuriCavePos = game::GetWorldMap()->GetEntryPos(0, ELPURI_CAVE);
3358 game::GetWorldMap()->GetWSquare(ElpuriCavePos)->ChangeOWTerrain(elpuricave::Spawn());
3359 game::GetWorldMap()->RevealEnvironment(ElpuriCavePos, 1);
3360 if (game::IsInWilderness()) game::GetWorldMap()->SendNewDrawRequest(); else game::SaveWorldMap();
3363 //game::Save();
3364 //game::Save(game::GetAutoSaveFileName());
3365 continue;
3367 if (w == "SetMoney") {
3368 ParseFuncArgs("n", args);
3369 sLong n = args[0].ival;
3370 if (n < 0) n = 0;
3371 if (mChar) mChar->SetMoney(n);
3372 continue;
3374 if (w == "EditMoney") {
3375 ParseFuncArgs("n", args);
3376 sLong n = args[0].ival;
3377 if (mChar) mChar->EditMoney(n);
3378 continue;
3380 if (w == "AddMessage") {
3381 ParseFuncArgs("*", args);
3382 festring s;
3383 for (uInt f = 0; f < args.size(); f++) {
3384 const FuncArg &a = args[f];
3385 if (a.type == FARG_STRING) s << a.sval; else s << a.ival;
3387 ADD_MESSAGE("%s", s.CStr());
3388 continue;
3390 if (w == "EatThisEvent") {
3391 if (AllowScript) ABORT("'EatThisEvent' forbidden in AllowScripts in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3392 eaten = true;
3393 continue;
3395 if (w == "Disallow") {
3396 if (!AllowScript) ABORT("'Disallow' forbidden in not-AllowScripts in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3397 eaten = false;
3398 continue;
3400 ABORT("Unknown function [%s] in file %s at line %d!", w.CStr(), mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3401 //if (mFEStack.top()->ReadWord() != ";") ABORT("';' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3403 //ABORT("Invalid term in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3405 //fprintf(stderr, "------------\n");
3406 return eaten;
3410 //TODO: cache event scripts
3411 truth game::RunOnEvent (cfestring &ename) {
3412 static std::vector<festring> scriptFiles;
3413 static truth cached = false;
3414 truth res = false;
3416 character *old = mChar;
3417 mChar = PLAYER;
3418 if (!cached) {
3419 cached = true;
3420 for (int fno = 99; fno >= -1; fno--) {
3421 festring cfname;
3422 cfname << game::GetGameDir() << "script/onevent";
3423 if (fno >= 0) {
3424 char bnum[8];
3425 sprintf(bnum, "_%02d", fno);
3426 cfname << bnum;
3428 cfname << ".dat";
3429 if (!inputfile::fileExists(cfname)) continue;
3430 TextInput *ifl = new TextInputFile(cfname, &game::GetGlobalValueMap(), false);
3431 if (!ifl->IsOpen()) {
3432 delete ifl;
3433 continue;
3435 scriptFiles.push_back(cfname);
3436 ifl->setGetVarCB(game::ldrGetVar);
3437 mFEStack.push(ifl);
3439 } else {
3440 for (unsigned int f = 0; f < scriptFiles.size(); ++f) {
3441 festring cfname = scriptFiles[f];
3442 TextInput *ifl = new TextInputFile(cfname, &game::GetGlobalValueMap(), false);
3443 if (!ifl->IsOpen()) {
3444 delete ifl;
3445 continue;
3447 ifl->setGetVarCB(game::ldrGetVar);
3448 mFEStack.push(ifl);
3452 festring w;
3453 while (GetWord(w)) {
3454 if (w != "on") ABORT("'on' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3455 mFEStack.top()->ReadWord(w, true);
3456 truth doIt = (w==ename);
3457 if (doIt && !res) {
3458 res = DoOnEvent(false);
3459 } else {
3460 // skip
3461 SkipBlock(false);
3464 mChar = old;
3465 return res;
3469 truth game::RunOnEventStr (cfestring &ename, cfestring &str) {
3470 truth res = false;
3471 if (str.GetSize() < 1) return false;
3472 //fprintf(stderr, "=============\n%s=============\n", str.CStr());
3473 TextInput *ifl = new MemTextFile("<memory>", str, &game::GetGlobalValueMap());
3474 ifl->setGetVarCB(game::ldrGetVar);
3475 mFEStack.push(ifl);
3476 festring w;
3477 //fprintf(stderr, "=============\n", str.CStr());
3478 //fprintf(stderr, "event: [%s]\n", ename.CStr());
3479 //fprintf(stderr, "---\n%s---\n", str.CStr());
3480 while (GetWord(w)) {
3481 if (w != "on") ABORT("'on' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3482 mFEStack.top()->ReadWord(w, true);
3483 //fprintf(stderr, "on: [%s]\n", w.CStr());
3484 truth doIt = (w==ename);
3485 if (doIt && !res) {
3486 //fprintf(stderr, " do it\n");
3487 res = DoOnEvent(false);
3488 } else {
3489 // skip
3490 //fprintf(stderr, " skip it\n");
3491 SkipBlock(false);
3494 return res;
3498 truth game::RunOnCharEvent (character *who, cfestring &ename) {
3499 truth res = false;
3500 if (!who) return false;
3501 character *old = mChar;
3502 mChar = who;
3503 res = RunOnEventStr(ename, who->mOnEvents);
3504 if (!res) res = RunOnEventStr(ename, who->GetProtoType()->mOnEvents);
3505 mChar = old;
3506 return res;
3510 truth game::RunOnItemEvent (item *what, cfestring &ename) {
3511 truth res = false;
3512 if (!what) return false;
3513 item *old = mItem;
3514 mItem = what;
3515 res = RunOnEventStr(ename, what->mOnEvents);
3516 if (!res) res = RunOnEventStr(ename, what->GetProtoType()->mOnEvents);
3517 mItem = old;
3518 return res;
3522 festring game::ldrGetVar (TextInput *fl, cfestring &name) {
3523 //fprintf(stderr, "GETVAR: [%s]\n", name.CStr());
3524 if (name == "player_name") {
3525 return game::GetPlayerName();
3527 if (name == "money") {
3528 festring res;
3529 if (!mChar) return "0";
3530 res << mChar->GetMoney();
3531 return res;
3533 if (name == "name") {
3534 if (!mChar) return "";
3535 return mChar->GetAssignedName();
3537 if (name == "team") {
3538 festring res;
3539 if (!mChar) return "";
3540 res << mChar->GetTeam()->GetID();
3541 return res;
3543 if (name == "friendly") {
3544 festring res;
3545 if (!mChar || !PLAYER || mChar->GetRelation(PLAYER) != HOSTILE) return "tan";
3546 return "";
3548 if (name == "hostile") {
3549 festring res;
3550 if (!mChar || !PLAYER) return "";
3551 if (mChar->GetRelation(PLAYER) == HOSTILE) return "tan";
3552 return "";
3554 if (name == "has_item") {
3555 std::vector<FuncArg> args;
3556 ParseFuncArgs("s", args, fl, true);
3558 if (PLAYER) {
3559 itemvector items;
3560 festring s = args[0].sval;
3562 //fprintf(stderr, "looking for [%s]\n", s.CStr());
3563 PLAYER->GetStack()->FillItemVector(items);
3564 for (unsigned int f = 0; f < items.size(); ++f) {
3565 for (uInt c = 0; c < items[f]->GetDataBase()->Alias.Size; ++c) {
3566 //fprintf(stderr, "%u:%u: [%s]\n", f, c, items[f]->GetDataBase()->Alias[c].CStr());
3567 if (s.CompareIgnoreCase(items[f]->GetDataBase()->Alias[c]) == 0) {
3568 //fprintf(stderr, " FOUND!\n");
3569 return "tan";
3573 //fprintf(stderr, "checking equipment...\n");
3574 for (int f = 0; f < PLAYER->GetEquipments(); ++f) {
3575 item *it = PLAYER->GetEquipment(f);
3577 if (it) {
3578 for (uInt c = 0; c < it->GetDataBase()->Alias.Size; ++c) {
3579 //fprintf(stderr, "%u:%u: [%s]\n", f, c, it->GetDataBase()->Alias[c].CStr());
3580 if (s.CompareIgnoreCase(it->GetDataBase()->Alias[c]) == 0) {
3581 //fprintf(stderr, " FOUND!\n");
3582 return "tan";
3588 return "";
3590 //if (name == "type") return mVarType;
3591 ABORT("unknown variable: %s", name.CStr());
3592 return "";
3596 truth game::CheckDropLeftover (item *i) {
3597 if (i->IsBottle() && !ivanconfig::GetAutoDropBottles()) return false;
3598 if (i->IsCan() && !ivanconfig::GetAutoDropCans()) return false;
3599 if (!ivanconfig::GetAutoDropLeftOvers()) return false;
3600 return true;
3604 truth game::RunAllowScriptStr (cfestring &str) {
3605 truth res = true;
3606 if (str.GetSize() < 1) return true;
3607 //fprintf(stderr, "====\n%s\n====\n", str.CStr());
3608 TextInput *ifl = new MemTextFile("<memory>", str, &game::GetGlobalValueMap());
3609 ifl->setGetVarCB(game::ldrGetVar);
3610 mFEStack.push(ifl);
3611 res = DoOnEvent(true, true);
3612 //fprintf(stderr, "mFEStack: %u\n", mFEStack.size());
3613 return res;