entities without definitions in script will be removed from game; it is now possible...
[k8-i-v-a-n.git] / src / game / game.cpp
blob1ddd43641e661fd35e3ba4af64f0c0554993f5f9
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"
48 #include "wterra.h"
50 #define SAVE_FILE_VERSION 134 // Increment this if changes make savefiles incompatible
51 #define BONE_FILE_VERSION 119 // Increment this if changes make bonefiles incompatible
53 #define LOADED 0
54 #define NEW_GAME 1
55 #define BACK 2
58 std::stack<TextInput *> game::mFEStack;
59 character *game::mChar = 0;
60 ccharacter *game::mActor = 0;
61 ccharacter *game::mSecondActor = 0;
62 item *game::mItem = 0;
63 int game::mResult = 0;
66 int game::CurrentLevelIndex;
67 truth game::InWilderness = false;
68 worldmap* game::WorldMap;
69 area* game::AreaInLoad;
70 square* game::SquareInLoad;
71 dungeon** game::Dungeon;
72 int game::CurrentDungeonIndex;
73 feuLong game::NextCharacterID = 1;
74 feuLong game::NextItemID = 1;
75 feuLong game::NextTrapID = 1;
76 team** game::Team;
77 feuLong game::LOSTick;
78 v2 game::CursorPos(-1, -1);
79 truth game::Zoom;
80 truth game::Generating = false;
81 double game::AveragePlayerArmStrengthExperience;
82 double game::AveragePlayerLegStrengthExperience;
83 double game::AveragePlayerDexterityExperience;
84 double game::AveragePlayerAgilityExperience;
85 int game::Teams;
86 int game::Dungeons;
87 int game::StoryState;
88 /* */
89 int game::XinrochTombStoryState;
90 int game::MondedrPass;
91 int game::RingOfThieves;
92 int game::Masamune;
93 int game::Muramasa;
94 int game::LoricatusHammer;
95 int game::Liberator;
96 int game::OmmelBloodMission;
97 int game::RegiiTalkState;
98 /* */
99 massacremap game::PlayerMassacreMap;
100 massacremap game::PetMassacreMap;
101 massacremap game::MiscMassacreMap;
102 sLong game::PlayerMassacreAmount = 0;
103 sLong game::PetMassacreAmount = 0;
104 sLong game::MiscMassacreAmount = 0;
105 boneidmap game::BoneItemIDMap;
106 boneidmap game::BoneCharacterIDMap;
107 truth game::TooGreatDangerFoundTruth;
108 itemvectorvector game::ItemDrawVector;
109 charactervector game::CharacterDrawVector;
110 truth game::SumoWrestling;
111 liquid* game::GlobalRainLiquid;
112 v2 game::GlobalRainSpeed;
113 sLong game::GlobalRainTimeModifier;
114 truth game::PlayerSumoChampion;
115 truth game::PlayerSolicitusChampion;
116 truth game::TouristHasSpider;
117 feuLong game::SquarePartEmitationTick = 0;
118 sLong game::Turn;
119 truth game::PlayerRunning;
120 character* game::LastPetUnderCursor;
121 charactervector game::PetVector;
122 double game::DangerFound;
123 int game::OldAttribute[ATTRIBUTES];
124 int game::NewAttribute[ATTRIBUTES];
125 int game::LastAttributeChangeTick[ATTRIBUTES];
126 int game::NecroCounter;
127 int game::CursorData;
128 truth game::CausePanicFlag;
130 truth game::Loading = false;
131 truth game::JumpToPlayerBe = false;
132 truth game::InGetCommand = false;
133 character *game::Petrus = 0;
134 time_t game::TimePlayedBeforeLastLoad;
135 time_t game::LastLoad;
136 time_t game::GameBegan;
137 truth game::PlayerHasReceivedAllGodsKnownBonus;
139 festring game::AutoSaveFileName = game::GetSavePath()+"AutoSave";
140 cchar *const game::Alignment[] = { "L++", "L+", "L", "L-", "N+", "N=", "N-", "C+", "C", "C-", "C--" };
141 god **game::God;
143 cint game::MoveNormalCommandKey[] = { KEY_HOME, KEY_UP, KEY_PAGE_UP, KEY_LEFT, KEY_RIGHT, KEY_END, KEY_DOWN, KEY_PAGE_DOWN, '.' };
144 int game::MoveAbnormalCommandKey[] = { '7','8','9','u','o','j','k','l','.' };
146 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) };
147 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) };
148 cv2 game::BasicMoveVector[] = { v2(-1, 0), v2(1, 0), v2(0, -1), v2(0, 1) };
149 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) };
150 cint game::LargeMoveDirection[] = { 0, 1, 1, 2, 3, 4, 3, 4, 5, 6, 6, 7, 8, 8, 8, 8 };
152 truth game::LOSUpdateRequested = false;
153 uChar ***game::LuxTable = 0;
154 truth game::Running;
155 character *game::Player;
156 v2 game::Camera(0, 0);
157 feuLong game::Tick;
158 gamescript *game::GameScript = 0;
159 valuemap game::GlobalValueMap;
160 dangermap game::DangerMap;
161 int game::NextDangerIDType;
162 int game::NextDangerIDConfigIndex;
163 characteridmap game::CharacterIDMap;
164 itemidmap game::ItemIDMap;
165 trapidmap game::TrapIDMap;
166 truth game::PlayerHurtByExplosion;
167 area *game::CurrentArea;
168 level *game::CurrentLevel;
169 wsquare ***game::CurrentWSquareMap;
170 lsquare ***game::CurrentLSquareMap;
171 festring game::DefaultPolymorphTo;
172 festring game::DefaultSummonMonster;
173 festring game::DefaultWish;
174 festring game::DefaultChangeMaterial;
175 festring game::DefaultDetectMaterial;
176 festring game::DefaultTeam;
177 truth game::WizardMode;
178 int game::SeeWholeMapCheatMode;
179 truth game::GoThroughWallsCheat;
180 int game::QuestMonstersFound;
181 bitmap *game::BusyAnimationCache[32];
182 festring game::PlayerName;
183 feuLong game::EquipmentMemory[MAX_EQUIPMENT_SLOTS];
184 olterrain *game::MonsterPortal;
185 std::vector<v2> game::SpecialCursorPos;
186 std::vector<int> game::SpecialCursorData;
187 cbitmap *game::EnterImage;
188 v2 game::EnterTextDisplacement;
190 std::vector<festring> game::mModuleList;
191 truth game::mImmediateSave = false;
194 // ////////////////////////////////////////////////////////////////////////// //
195 owterrain **game::pois = nullptr;
196 int game::poisSize = 0;
199 int game::poiCount () { return poisSize; }
202 owterrain *game::poiByIndex (int idx, truth abortOnNotFound) {
203 if (idx >= 0 && idx < poisSize) {
204 if (!abortOnNotFound || pois[idx]) return pois[idx];
206 if (abortOnNotFound) ABORT("POI with index %d not found!", idx);
207 return nullptr;
211 owterrain *game::poi (cfestring &name, truth abortOnNotFound) {
212 auto pcfg = FindGlobalValue(name, -1);
213 //fprintf(stderr, "<%s>=%d\n", name.CStr(), pcfg);
214 for (int f = 0; f < poisSize; ++f) {
215 if (pois[f] && pois[f]->GetConfig() == pcfg) return pois[f];
217 if (abortOnNotFound) {
218 //*(int*)(0) = 42;
219 ABORT("POI config '%s' not found!", name.CStr());
221 return nullptr;
225 //owterrain *game::alienvesselPOI () { return poi("ALIEN_VESSEL", true); }
226 owterrain *game::attnamPOI () { return poi("ATTNAM", true); }
227 owterrain *game::darkforestPOI () { return poi("DARK_FOREST", true); }
228 //owterrain *game::dragontowerPOI () { return poi("DRAGON_TOWER", true); }
229 owterrain *game::elpuricavePOI () { return poi("ELPURI_CAVE", true); }
230 owterrain *game::mondedrPOI () { return poi("MONDEDR", true); }
231 owterrain *game::muntuoPOI () { return poi("MUNTUO", true); }
232 owterrain *game::newattnamPOI () { return poi("NEW_ATTNAM", true); }
233 owterrain *game::underwatertunnelPOI () { return poi("UNDER_WATER_TUNNEL", true); }
234 owterrain *game::underwatertunnelexitPOI () { return poi("UNDER_WATER_TUNNEL_EXIT", true); }
235 owterrain *game::xinrochtombPOI () { return poi("XINROCH_TOMB", true); }
238 // ////////////////////////////////////////////////////////////////////////// //
239 // -1: none
240 int game::MoveVectorToDirection (cv2 &mv) {
241 for (int c = 0; c < 9; ++c) if (MoveVector[c] == mv) return c;
242 return -1;
246 char game::GetAbnormalMoveKey (int idx) {
247 if (idx < 0 || idx > 8) return 0;
248 return MoveAbnormalCommandKey[idx];
252 void game::SetAbnormalMoveKey (int idx, char ch) {
253 if (idx >= 0 && idx <= 8) MoveAbnormalCommandKey[idx] = ch;
257 void game::AddCharacterID (character *Char, feuLong ID) {
258 /*k8:??? if (CharacterIDMap.find(ID) != CharacterIDMap.end())
259 int esko = esko = 2;*/
260 CharacterIDMap.insert(std::make_pair(ID, Char));
264 void game::RemoveCharacterID (feuLong ID) {
265 /*k8:??? if (CharacterIDMap.find(ID) == CharacterIDMap.end())
266 int esko = esko = 2;*/
267 CharacterIDMap.erase(CharacterIDMap.find(ID));
271 void game::AddItemID (item *Item, feuLong ID) {
272 /*k8:??? if (ItemIDMap.find(ID) != ItemIDMap.end())
273 int esko = esko = 2;*/
274 ItemIDMap.insert(std::make_pair(ID, Item));
278 void game::RemoveItemID (feuLong ID) {
279 /*k8:??? if(ID && ItemIDMap.find(ID) == ItemIDMap.end())
280 int esko = esko = 2;*/
281 if (ID) ItemIDMap.erase(ItemIDMap.find(ID));
285 void game::UpdateItemID (item *Item, feuLong ID) {
286 /*k8:??? if(ItemIDMap.find(ID) == ItemIDMap.end())
287 int esko = esko = 2;*/
288 ItemIDMap.find(ID)->second = Item;
292 void game::AddTrapID (entity *Trap, feuLong ID) {
293 /*k8:??? if(TrapIDMap.find(ID) != TrapIDMap.end())
294 int esko = esko = 2;*/
295 if (ID) TrapIDMap.insert(std::make_pair(ID, Trap));
299 void game::RemoveTrapID (feuLong ID) {
300 /*k8:??? if(ID && TrapIDMap.find(ID) == TrapIDMap.end())
301 int esko = esko = 2;*/
302 if (ID) TrapIDMap.erase(TrapIDMap.find(ID));
306 void game::UpdateTrapID (entity *Trap, feuLong ID) {
307 /*k8:??? if(TrapIDMap.find(ID) == TrapIDMap.end())
308 int esko = esko = 2;*/
309 TrapIDMap.find(ID)->second = Trap;
313 const dangermap &game::GetDangerMap () { return DangerMap; }
314 void game::ClearItemDrawVector () { ItemDrawVector.clear(); }
315 void game::ClearCharacterDrawVector () { CharacterDrawVector.clear(); }
318 void game::InitScript () {
319 TextInputFile ScriptFile(GetGameDir()+"script/dungeon.dat", &GlobalValueMap);
320 GameScript = new gamescript;
321 GameScript->ReadFrom(ScriptFile);
322 // load dungeons from modules
323 for (auto &modname : mModuleList) {
324 festring infname = game::GetGameDir()+"script/"+modname+"/dungeon.dat";
325 if (inputfile::fileExists(infname)) {
326 TextInputFile ifl(infname, &game::GetGlobalValueMap());
327 GameScript->ReadFrom(ifl);
330 GameScript->RandomizeLevels();
334 void game::DeInitPlaces () {
335 for (int f = 0; f < poisSize; ++f) delete pois[f];
336 delete [] pois;
337 poisSize = 0;
338 pois = nullptr;
342 void game::InitPlaces () {
343 // spawn all POI configs (except 0, which is abstract base)
344 //fprintf(stderr, "owterras: %u\n", protocontainer<owterrain>::GetSize());
345 auto xtype = protocontainer<owterrain>::SearchCodeName("owterrain");
346 if (!xtype) ABORT("Your worldmap is dull and empty.");
347 const owterrain::prototype* proto = protocontainer<owterrain>::GetProto(xtype);
348 if (!proto) ABORT("wtf?!");
349 const owterrain::database *const *configs = proto->GetConfigData();
350 int cfgcount = proto->GetConfigSize();
351 //fprintf(stderr, "owterrain configs: %d\n", cfgcount);
352 // count "good" pois
353 int goodPOIs = 0;
354 for (int f = 0; f < cfgcount; ++f) {
355 auto cfg = configs[f];
356 if (cfg->IsAbstract) continue;
357 if (cfg->Config == 0) continue; // base config, skip it (just in case)
358 if (!cfg->CanBeGenerated) continue;
359 if (cfg->Probability < 1) continue;
360 //fprintf(stderr, "***POI <%s>\n", cfg->CfgStrName.CStr());
361 ++goodPOIs;
363 // at least 4: attnam, new attnam, and two entries to underwater tunner
364 if (goodPOIs < 4) ABORT("The world is so dull and boring...");
365 pois = new owterrain *[goodPOIs];
366 poisSize = 0; // will be used as counter
367 for (int f = 0; f < cfgcount; ++f) {
368 auto cfg = configs[f];
369 if (cfg->IsAbstract) continue;
370 if (cfg->Config == 0) continue; // base config, skip it (just in case)
371 if (!cfg->CanBeGenerated) continue;
372 if (cfg->Probability < 1) continue;
373 //if (!ConfigData[c2]->IsAbstract && ConfigData[c2]->IsAutoInitializable)
374 //fprintf(stderr, "POI <%s>\n", cfg->CfgStrName.CStr());
375 owterrain *poi = proto->Spawn(cfg->Config);
376 poi->SetRevealed(false); // for now
377 poi->SetPlaced(false); // for now
378 if (poisSize >= goodPOIs) ABORT("Somehow i cannot count right!");
379 pois[poisSize++] = poi;
384 void game::RevealPOI (owterrain *terra) {
385 if (!terra) return;
386 for (int f = 0; f < poisSize; ++f) {
387 if (pois[f] == terra) {
388 if (!IsInWilderness()) LoadWorldMap();
389 GetWorldMap()->poiPlaceAtMap(terra, true); // force revealing
390 //GetWorldMap()->RevealEnvironment(terra->GetPosition(), 1);
391 if (IsInWilderness()) {
392 //fprintf(stderr, "NEW DRAW REQUEST SENT!\n");
393 GetWorldMap()->SendNewDrawRequest();
394 } else {
395 // for some reason doing this in wilderness temporarily resets player postion and corrupt saves
396 SaveWorldMap();
403 truth game::Init (cfestring &Name) {
404 if (Name.IsEmpty()) {
405 if (ivanconfig::GetDefaultName().IsEmpty()) {
406 PlayerName.Empty();
407 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;
408 } else {
409 PlayerName = ivanconfig::GetDefaultName();
411 } else {
412 PlayerName = Name;
415 mkdir(GetSavePath().CStr(), S_IRWXU|S_IRWXG);
416 mkdir(GetBonePath().CStr(), S_IRWXU|S_IRWXG);
418 LOSTick = 2;
419 DangerFound = 0;
420 CausePanicFlag = false;
421 InitPlaces();
422 pool::KillBees();
423 mImmediateSave = false;
425 switch (Load(SaveName(PlayerName))) {
426 case LOADED: {
427 globalwindowhandler::InstallControlLoop(AnimationController);
428 SetIsRunning(true);
429 SetForceJumpToPlayerBe(true);
430 GetCurrentArea()->SendNewDrawRequest();
431 SendLOSUpdateRequest();
432 ADD_MESSAGE("Game loaded successfully.");
433 } return true;
434 case NEW_GAME: {
435 iosystem::TextScreen(CONST_S(
436 "You couldn't possibly have guessed this day would differ from any other.\n"
437 "It began just as always. You woke up at dawn and drove off the giant spider\n"
438 "resting on your face. On your way to work you had serious trouble avoiding\n"
439 "the lions and pythons roaming wild around the village. After getting kicked\n"
440 "by colony masters for being late you performed your twelve-hour routine of\n"
441 "climbing trees, gathering bananas, climbing trees, gathering bananas, chasing\n"
442 "monkeys that stole the first gathered bananas, carrying bananas to the village\n"
443 "and trying to look happy when real food was distributed.\n\n"
444 "Finally you were about to enjoy your free time by taking a quick dip in the\n"
445 "nearby crocodile bay. However, at this point something unusual happened.\n"
446 "You were summoned to the mansion of Richel Decos, the viceroy of the\n"
447 "colony, and were led directly to him."));
448 iosystem::TextScreen(CONST_S(
449 "\"I have a task for you, citizen\", said the viceroy picking his golden\n"
450 "teeth, \"The market price of bananas has taken a deep dive and yet the\n"
451 "central government is about to raise taxes. I have sent appeals to high\n"
452 "priest Petrus but received no response. I fear my enemies in Attnam are\n"
453 "plotting against me and intercepting my messages before they reach him!\"\n\n"
454 "\"That is why you must travel to Attnam with a letter I'll give you and\n"
455 "deliver it to Petrus directly. Alas, you somehow have to cross the sea\n"
456 "between. Because it's winter, all Attnamese ships are trapped by ice and\n"
457 "I have none. Therefore you must venture through the small underwater tunnel\n"
458 "connecting our islands. It is infested with monsters, but since you have\n"
459 "stayed alive here so long, the trip will surely cause you no trouble.\"\n\n"
460 "You have never been so happy! According to the mansion's traveling\n"
461 "brochures, Attnam is a peaceful but bustling world city on a beautiful\n"
462 "snowy fell surrounded by frozen lakes glittering in the arctic sun just\n"
463 "like the diamonds of the imperial treasury. Not that you would believe a\n"
464 "word. The point is that tomorrow you can finally forget your home and\n"
465 "face the untold adventures ahead."));
466 pool::RemoveEverything(); // memory leak!
467 InitPlaces(); // why not
468 CurrentLevel = 0;
469 globalwindowhandler::InstallControlLoop(AnimationController);
470 LOSTick = 2;
471 DangerFound = 0;
472 CausePanicFlag = false;
473 SetIsRunning(true);
474 InWilderness = true;
475 iosystem::TextScreen(CONST_S("Generating game...\n\nThis may take some time, please wait."), ZERO_V2, WHITE, false, true, &BusyAnimation);
476 igraph::CreateBackGround(GRAY_FRACTAL);
477 NextCharacterID = 1;
478 NextItemID = 1;
479 NextTrapID = 1;
480 InitScript();
481 CreateTeams();
482 CreateGods();
483 SetPlayer(playerkind::Spawn());
484 Player->SetAssignedName(PlayerName);
485 Player->SetTeam(GetTeam(PLAYER_TEAM));
486 Player->SetNP(SATIATED_LEVEL);
487 for (int c = 0; c < ATTRIBUTES; ++c) {
488 if (c != ENDURANCE) Player->EditAttribute(c, (RAND()&1)-(RAND()&1));
489 Player->EditExperience(c, 500, 1<<11);
491 Player->SetMoney(Player->GetMoney()+RAND()%11);
492 GetTeam(0)->SetLeader(Player);
493 InitDangerMap();
495 pool::KillBees();
496 if (Player->IsEnabled()) { Player->Disable(); Player->Enable(); }
498 Petrus = 0;
499 InitDungeons();
500 SetCurrentArea(WorldMap = new worldmap(128, 128));
501 CurrentWSquareMap = WorldMap->GetMap();
502 WorldMap->Generate();
503 UpdateCamera();
504 SendLOSUpdateRequest();
505 Tick = 0;
506 Turn = 0;
507 InitPlayerAttributeAverage();
508 StoryState = 0;
509 /* */
510 XinrochTombStoryState = 0;
511 MondedrPass = 0;
512 RingOfThieves = 0;
513 Masamune = 0;
514 Muramasa = 0;
515 LoricatusHammer = 0;
516 Liberator = 0;
517 OmmelBloodMission = 0;
518 RegiiTalkState = 0;
519 /* */
520 PlayerMassacreMap.clear();
521 PetMassacreMap.clear();
522 MiscMassacreMap.clear();
523 PlayerMassacreAmount = PetMassacreAmount = MiscMassacreAmount = 0;
524 DefaultPolymorphTo.Empty();
525 DefaultSummonMonster.Empty();
526 DefaultWish.Empty();
527 DefaultChangeMaterial.Empty();
528 DefaultDetectMaterial.Empty();
529 DefaultTeam.Empty();
530 Player->GetStack()->AddItem(encryptedscroll::Spawn());
531 if (ivanconfig::GetDefaultPetName() != "_none_") {
532 character *Doggie = dog::Spawn();
533 Doggie->SetTeam(GetTeam(PLAYER_TEAM));
534 GetWorldMap()->GetPlayerGroup().push_back(Doggie);
535 Doggie->SetAssignedName(ivanconfig::GetDefaultPetName());
537 WizardMode = false;
538 SeeWholeMapCheatMode = MAP_HIDDEN;
539 GoThroughWallsCheat = false;
540 SumoWrestling = false;
541 GlobalRainTimeModifier = 2048-(RAND()&4095);
542 PlayerSumoChampion = false;
543 PlayerSolicitusChampion = false;
544 TouristHasSpider = false;
545 protosystem::InitCharacterDataBaseFlags();
546 memset(EquipmentMemory, 0, sizeof(EquipmentMemory));
547 PlayerRunning = false;
548 InitAttributeMemory();
549 NecroCounter = 0;
550 GameBegan = time(0);
551 LastLoad = time(0);
552 TimePlayedBeforeLastLoad = time::GetZeroTime();
553 /*k8: damn! seems that this is field, not local! bool PlayerHasReceivedAllGodsKnownBonus = false; */
554 PlayerHasReceivedAllGodsKnownBonus = false;
555 ADD_MESSAGE("You commence your journey to Attnam. Use direction keys to move, '>' to enter an area and '?' to view other commands.");
556 game::ClearEventData();
557 RunOnEvent("game_start");
558 if (IsXMas()) {
559 item *Present = banana::Spawn();
560 Player->GetStack()->AddItem(Present);
561 ADD_MESSAGE("Atavus is happy today! He gives you %s.", Present->CHAR_NAME(INDEFINITE));
563 } return true;
564 default: return false;
569 void game::DeInit () {
570 pool::BurnHell();
571 DeInitPlaces();
572 delete WorldMap;
573 WorldMap = 0;
574 if (Dungeon) {
575 for (int c = 1; c < Dungeons; ++c) delete Dungeon[c];
576 delete [] Dungeon;
577 Dungeon = 0;
579 if (God) {
580 for (int c = 1; c <= GODS; ++c) delete God[c]; // sorry, Valpuri!
581 delete [] God;
582 God = 0;
584 if (Team) {
585 for (int c = 0; c < Teams; ++c) delete Team[c];
586 delete [] Team;
587 Team = 0;
589 delete GameScript;
590 GameScript = 0;
591 msgsystem::Format();
592 DangerMap.clear();
596 void game::ScheduleImmediateSave () {
597 game::Save();
598 game::Save(game::GetAutoSaveFileName());
599 mImmediateSave = false;
603 void game::Run () {
604 for (;;) {
605 if (mImmediateSave) {
606 fprintf(stderr, "force saving!\n");
607 mImmediateSave = false;
608 game::Save();
609 game::Save(game::GetAutoSaveFileName());
612 if (!InWilderness) {
613 /* Temporary places */
614 static int Counter = 0;
615 if (++Counter == 10) {
616 CurrentLevel->GenerateMonsters();
617 Counter = 0;
619 if (CurrentDungeonIndex == ELPURI_CAVE && CurrentLevelIndex == ZOMBIE_LEVEL && !RAND_N(1000+NecroCounter)) {
620 character *Char = necromancer::Spawn(RAND_N(4) ? APPRENTICE_NECROMANCER : MASTER_NECROMANCER);
621 v2 Pos;
622 for (int c2 = 0; c2 < 30; ++c2) {
623 Pos = GetCurrentLevel()->GetRandomSquare(Char);
624 if (abs(int(Pos.X)-Player->GetPos().X) > 20 || abs(int(Pos.Y)-Player->GetPos().Y) > 20) break;
626 if (Pos != ERROR_V2) {
627 Char->SetTeam(GetTeam(MONSTER_TEAM));
628 Char->PutTo(Pos);
629 Char->SetGenerationDanger(GetCurrentLevel()->GetDifficulty());
630 Char->SignalGeneration();
631 Char->SignalNaturalGeneration();
632 ivantime Time;
633 GetTime(Time);
634 int Modifier = Time.Day - EDIT_ATTRIBUTE_DAY_MIN;
635 if (Modifier > 0) Char->EditAllAttributes(Modifier >> EDIT_ATTRIBUTE_DAY_SHIFT);
636 NecroCounter += 50;
637 } else {
638 delete Char;
639 //Char->SendToHell(); // k8:equipment crash?
643 if (!(GetTick() % 1000)) CurrentLevel->CheckSunLight();
645 if ((CurrentDungeonIndex == NEW_ATTNAM || CurrentDungeonIndex == ATTNAM) && CurrentLevelIndex == 0) {
646 sLong OldVolume = GlobalRainLiquid->GetVolume();
647 sLong NewVolume = Max(sLong(sin((Tick+GlobalRainTimeModifier)*0.0003)*300-150), 0);
648 if (NewVolume && !OldVolume) CurrentLevel->EnableGlobalRain();
649 else if(!NewVolume && OldVolume) CurrentLevel->DisableGlobalRain();
650 GlobalRainLiquid->SetVolumeNoSignals(NewVolume);
654 try {
655 pool::Be();
656 pool::BurnHell();
657 IncreaseTick();
658 ApplyDivineTick();
660 } catch (quitrequest) {
661 break;
662 } catch (areachangerequest) {
668 void game::InitLuxTable () {
669 if (!LuxTable) {
670 Alloc3D(LuxTable, 256, 33, 33);
671 for (int c = 0; c < 0x100; ++c)
672 for (int x = 0; x < 33; ++x)
673 for (int y = 0; y < 33; ++y) {
674 int X = x-16, Y = y-16;
675 LuxTable[c][x][y] = int(c/(double(X*X+Y*Y)/128+1));
677 atexit(DeInitLuxTable);
682 void game::DeInitLuxTable () {
683 delete [] LuxTable;
684 LuxTable = 0;
688 void game::UpdateCameraX () {
689 UpdateCameraX(Player->GetPos().X);
693 void game::UpdateCameraY () {
694 UpdateCameraY(Player->GetPos().Y);
698 void game::UpdateCameraX (int X) {
699 UpdateCameraCoordinate(Camera.X, X, GetCurrentArea()->GetXSize(), GetScreenXSize());
703 void game::UpdateCameraY (int Y) {
704 UpdateCameraCoordinate(Camera.Y, Y, GetCurrentArea()->GetYSize(), GetScreenYSize());
708 void game::UpdateCameraCoordinate (int &Coordinate, int Center, int Size, int ScreenSize) {
709 int OldCoordinate = Coordinate;
710 if (Size < ScreenSize) Coordinate = (Size-ScreenSize)>>1;
711 else if(Center < ScreenSize>>1) Coordinate = 0;
712 else if(Center > Size-(ScreenSize>>1)) Coordinate = Size-ScreenSize;
713 else Coordinate = Center-(ScreenSize>>1);
714 if (Coordinate != OldCoordinate) GetCurrentArea()->SendNewDrawRequest();
718 cchar *game::Insult () {
719 static const char *insults[19] = {
720 "moron",
721 "silly",
722 "idiot",
723 "airhead",
724 "jerk",
725 "dork",
726 "Mr. Mole",
727 "navastater",
728 "potatoes-for-eyes",
729 "lamer",
730 "mommo-for-brains",
731 "pinhead",
732 "stupid-headed person",
733 "software abuser",
734 "loser",
735 "peaballs",
736 "person-with-problems",
737 "unimportant user",
738 "hugger-mugger"
740 int n = RAND_N(18);
741 if (n < 0 || n > 18) n = 18;
742 return insults[n];
746 /* DefaultAnswer = REQUIRES_ANSWER the question requires an answer */
747 truth game::TruthQuestion (cfestring &String, int DefaultAnswer, int OtherKeyForTrue) {
748 festring xstr = String;
749 if (DefaultAnswer == NO) { DefaultAnswer = 'n'; xstr << " [\1Cy\2/\1RN\2]"; }
750 else if (DefaultAnswer == YES) { DefaultAnswer = 'y'; xstr << " [\1RY\2/\1Cn\2]"; }
751 else if (DefaultAnswer == REQUIRES_ANSWER) { xstr << " [\1Cy\2/\1Cn\2]"; }
752 else ABORT("Illegal TruthQuestion DefaultAnswer send!");
753 int FromKeyQuestion = KeyQuestion(/*String*/xstr, DefaultAnswer, 9, 'y', 'Y', 'n', 'N', 't', 'T', 'o', 'O', OtherKeyForTrue);
754 return
755 FromKeyQuestion == 'y' || FromKeyQuestion == 'Y' ||
756 FromKeyQuestion == 't' || FromKeyQuestion == 'T' ||
757 FromKeyQuestion == OtherKeyForTrue;
761 void game::DrawEverything () {
762 DrawEverythingNoBlit();
763 graphics::BlitDBToScreen();
767 truth game::OnScreen (v2 Pos) {
768 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();
772 void game::DrawEverythingNoBlit (truth AnimationDraw) {
773 if (LOSUpdateRequested && Player->IsEnabled()) {
774 if (!IsInWilderness()) GetCurrentLevel()->UpdateLOS(); else GetWorldMap()->UpdateLOS();
777 if (OnScreen(CursorPos)) {
778 if (!IsInWilderness() || CurrentWSquareMap[CursorPos.X][CursorPos.Y]->GetLastSeen() || GetSeeWholeMapCheatMode())
779 CurrentArea->GetSquare(CursorPos)->SendStrongNewDrawRequest();
780 else
781 DOUBLE_BUFFER->Fill(CalculateScreenCoordinates(CursorPos), TILE_V2, 0);
784 for (unsigned int c = 0; c < SpecialCursorPos.size(); ++c) {
785 if (OnScreen(SpecialCursorPos[c])) CurrentArea->GetSquare(SpecialCursorPos[c])->SendStrongNewDrawRequest();
788 globalwindowhandler::UpdateTick();
789 GetCurrentArea()->Draw(AnimationDraw);
790 Player->DrawPanel(AnimationDraw);
792 if (!AnimationDraw) msgsystem::Draw();
794 if (OnScreen(CursorPos)) {
795 v2 ScreenCoord = CalculateScreenCoordinates(CursorPos);
797 blitdata B = {
798 DOUBLE_BUFFER,
799 { 0, 0 },
800 { ScreenCoord.X, ScreenCoord.Y },
801 { TILE_SIZE, TILE_SIZE },
802 { 0 },
803 TRANSPARENT_COLOR,
804 ALLOW_ANIMATE|ALLOW_ALPHA
807 if (!IsInWilderness() && !GetSeeWholeMapCheatMode()) {
808 lsquare *Square = CurrentLSquareMap[CursorPos.X][CursorPos.Y];
809 if (Square->GetLastSeen() != GetLOSTick()) Square->DrawMemorized(B);
812 if (DoZoom()) {
813 B.Src = B.Dest;
814 B.Dest.X = RES.X - 96;
815 B.Dest.Y = RES.Y - 96;
816 B.Stretch = 5;
817 DOUBLE_BUFFER->StretchBlit(B);
820 igraph::DrawCursor(ScreenCoord, CursorData|CURSOR_SHADE);
823 if (Player->IsEnabled()) {
824 if (Player->IsSmall()) {
825 v2 Pos = Player->GetPos();
826 if (OnScreen(Pos)) {
827 v2 ScreenCoord = CalculateScreenCoordinates(Pos);
828 igraph::DrawCursor(ScreenCoord, Player->GetCursorData());
830 } else {
831 for (int f = 0; f < Player->GetSquaresUnder(); ++f) {
832 v2 Pos = Player->GetPos(f);
833 if (OnScreen(Pos)) {
834 v2 ScreenCoord = CalculateScreenCoordinates(Pos);
835 igraph::DrawCursor(ScreenCoord, Player->GetCursorData()|CURSOR_BIG, f);
841 for (unsigned int c = 0; c < SpecialCursorPos.size(); ++c) {
842 if (OnScreen(SpecialCursorPos[c])) {
843 v2 ScreenCoord = CalculateScreenCoordinates(SpecialCursorPos[c]);
844 igraph::DrawCursor(ScreenCoord, SpecialCursorData[c]);
845 GetCurrentArea()->GetSquare(SpecialCursorPos[c])->SendStrongNewDrawRequest();
851 truth game::Save (cfestring &SaveName) {
852 //fprintf(stderr, "plrpos=(%d,%d)\n", Player->GetPos().X, Player->GetPos().Y);
853 if (!GetCurrentArea() || !GetCurrentArea()->GetSquare(Player->GetPos()) || !GetCurrentArea()->GetSquare(Player->GetPos())->GetCharacter()) {
854 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);
855 return false;
857 DrawEverythingNoBlit();
858 #if defined(SGAME_SHOTS_IPU) || (!defined(HAVE_IMLIB2) && !defined(HAVE_LIBPNG))
859 DOUBLE_BUFFER->SaveScaledIPU(SaveName+".ipu", 0.8); //640; 320
860 #else
861 DOUBLE_BUFFER->SaveScaledPNG(SaveName+".png", 0.8); //640; 320
862 #endif
863 outputfile SaveFile(SaveName+".sav", ivanconfig::GetUseMaximumCompression());
864 SaveFile << int(SAVE_FILE_VERSION);
865 SaveFile << GameScript << CurrentDungeonIndex << CurrentLevelIndex << Camera;
866 SaveFile << WizardMode << SeeWholeMapCheatMode << GoThroughWallsCheat;
867 SaveFile << Tick << Turn << InWilderness << NextCharacterID << NextItemID << NextTrapID << NecroCounter;
868 SaveFile << SumoWrestling << PlayerSumoChampion << GlobalRainTimeModifier;
869 SaveFile << PlayerSolicitusChampion << TouristHasSpider;
870 // POIs
871 //fprintf(stderr, "saving POIs...\n");
872 SaveFile << (sLong)poisSize;
873 for (int pc = 0; pc < poisSize; ++pc) SaveFile << pois[pc];
874 //fprintf(stderr, "POIs saved (0x%08x)\n", (unsigned)SaveFile.TellPos());
876 femath::SavePRNG(SaveFile);
877 SaveFile << AveragePlayerArmStrengthExperience;
878 SaveFile << AveragePlayerLegStrengthExperience;
879 SaveFile << AveragePlayerDexterityExperience;
880 SaveFile << AveragePlayerAgilityExperience;
881 SaveFile << Teams << Dungeons << StoryState << PlayerRunning;
882 SaveFile << MondedrPass << RingOfThieves << Masamune << Muramasa << LoricatusHammer << Liberator;
883 SaveFile << OmmelBloodMission << RegiiTalkState << XinrochTombStoryState;
884 SaveFile << PlayerMassacreMap << PetMassacreMap << MiscMassacreMap;
885 SaveFile << PlayerMassacreAmount << PetMassacreAmount << MiscMassacreAmount;
886 SaveArray(SaveFile, EquipmentMemory, MAX_EQUIPMENT_SLOTS);
887 for (int c = 0; c < ATTRIBUTES; ++c) SaveFile << OldAttribute[c] << NewAttribute[c] << LastAttributeChangeTick[c];
888 for (int c = 1; c < Dungeons; ++c) SaveFile << Dungeon[c];
889 for (int c = 1; c <= GODS; ++c) SaveFile << God[c];
890 for (int c = 0; c < Teams; ++c) SaveFile << Team[c];
891 if (InWilderness) {
892 SaveWorldMap(SaveName, false);
893 } else {
894 GetCurrentDungeon()->SaveLevel(SaveName, CurrentLevelIndex, false);
896 SaveFile << Player->GetPos() << PlayerName;
897 msgsystem::Save(SaveFile);
898 SaveFile << DangerMap << NextDangerIDType << NextDangerIDConfigIndex;
899 SaveFile << DefaultPolymorphTo << DefaultSummonMonster;
900 SaveFile << DefaultWish << DefaultChangeMaterial << DefaultDetectMaterial << DefaultTeam;
901 SaveFile << GetTimeSpent();
902 /* or in more readable format: time() - LastLoad + TimeAtLastLoad */
903 SaveFile << PlayerHasReceivedAllGodsKnownBonus;
904 protosystem::SaveCharacterDataBaseFlags(SaveFile);
905 return true;
909 int game::Load (cfestring &SaveName) {
910 inputfile SaveFile(SaveName+".sav", false);
911 if (!SaveFile.IsOpen()) return NEW_GAME;
912 int Version;
913 SaveFile >> Version;
914 if (Version != SAVE_FILE_VERSION) {
915 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)) {
916 return NEW_GAME;
917 } else {
918 return BACK;
921 SaveFile >> GameScript >> CurrentDungeonIndex >> CurrentLevelIndex >> Camera;
922 SaveFile >> WizardMode >> SeeWholeMapCheatMode >> GoThroughWallsCheat;
923 SaveFile >> Tick >> Turn >> InWilderness >> NextCharacterID >> NextItemID >> NextTrapID >> NecroCounter;
924 SaveFile >> SumoWrestling >> PlayerSumoChampion >> GlobalRainTimeModifier;
925 SaveFile >> PlayerSolicitusChampion >> TouristHasSpider;
926 // POIs
927 sLong pcnt;
928 //fprintf(stderr, "loading POIs...\n");
929 SaveFile >> pcnt;
930 if (pcnt != poisSize) {
931 if (!iosystem::Menu(0, v2(RES.X >> 1, RES.Y >> 1), CONST_S("Sorry, this save is incompatible with the new version (POIs).\rStart new game?\r"), CONST_S("Yes\rNo\r"), LIGHT_GRAY)) {
932 return NEW_GAME;
933 } else {
934 return BACK;
937 for (int pc = 0; pc < pcnt; ++pc) {
938 delete pois[pc];
939 SaveFile >> pois[pc];
940 if (!pois[pc]) {
941 if (!iosystem::Menu(0, v2(RES.X >> 1, RES.Y >> 1), CONST_S("Sorry, this save is broken.\rStart new game?\r"), CONST_S("Yes\rNo\r"), LIGHT_GRAY)) {
942 return NEW_GAME;
943 } else {
944 return BACK;
948 //fprintf(stderr, "POIs loaded (0x%08x)\n", (unsigned)SaveFile.TellPos());
950 femath::LoadPRNG(SaveFile);
951 SaveFile >> AveragePlayerArmStrengthExperience;
952 SaveFile >> AveragePlayerLegStrengthExperience;
953 SaveFile >> AveragePlayerDexterityExperience;
954 SaveFile >> AveragePlayerAgilityExperience;
955 SaveFile >> Teams >> Dungeons >> StoryState >> PlayerRunning;
956 SaveFile >> MondedrPass >> RingOfThieves >> Masamune >> Muramasa >> LoricatusHammer >> Liberator;
957 SaveFile >> OmmelBloodMission >> RegiiTalkState >> XinrochTombStoryState;
958 SaveFile >> PlayerMassacreMap >> PetMassacreMap >> MiscMassacreMap;
959 SaveFile >> PlayerMassacreAmount >> PetMassacreAmount >> MiscMassacreAmount;
960 LoadArray(SaveFile, EquipmentMemory, MAX_EQUIPMENT_SLOTS);
961 for (int c = 0; c < ATTRIBUTES; ++c) SaveFile >> OldAttribute[c] >> NewAttribute[c] >> LastAttributeChangeTick[c];
962 Dungeon = new dungeon*[Dungeons];
963 Dungeon[0] = 0;
964 for (int c = 1; c < Dungeons; ++c) SaveFile >> Dungeon[c];
965 God = new god*[GODS+1];
966 God[0] = 0;
967 for (int c = 1; c <= GODS; ++c) SaveFile >> God[c];
968 Team = new team*[Teams];
969 for (int c = 0; c < Teams; ++c) SaveFile >> Team[c];
970 if (InWilderness) {
971 SetCurrentArea(LoadWorldMap(SaveName));
972 CurrentWSquareMap = WorldMap->GetMap();
973 igraph::CreateBackGround(GRAY_FRACTAL);
974 } else {
975 SetCurrentArea(CurrentLevel = GetCurrentDungeon()->LoadLevel(SaveName, CurrentLevelIndex));
976 CurrentLSquareMap = CurrentLevel->GetMap();
977 igraph::CreateBackGround(*CurrentLevel->GetLevelScript()->GetBackGroundType());
979 v2 Pos;
980 SaveFile >> Pos >> PlayerName;
981 SetPlayer(GetCurrentArea()->GetSquare(Pos)->GetCharacter());
982 if (!PLAYER) {
983 DeInit();
984 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)) {
985 return NEW_GAME;
986 } else {
987 return BACK;
990 msgsystem::Load(SaveFile);
991 SaveFile >> DangerMap >> NextDangerIDType >> NextDangerIDConfigIndex;
992 SaveFile >> DefaultPolymorphTo >> DefaultSummonMonster;
993 SaveFile >> DefaultWish >> DefaultChangeMaterial >> DefaultDetectMaterial >> DefaultTeam;
994 SaveFile >> TimePlayedBeforeLastLoad;
995 SaveFile >> PlayerHasReceivedAllGodsKnownBonus;
996 LastLoad = time(0);
997 protosystem::LoadCharacterDataBaseFlags(SaveFile);
998 return LOADED;
1002 festring game::SaveName (cfestring &Base) {
1003 festring SaveName = GetSavePath();
1004 if (!Base.GetSize()) SaveName << PlayerName; else SaveName << Base;
1005 for (festring::sizetype c = 0; c < SaveName.GetSize(); ++c) if (SaveName[c] == ' ') SaveName[c] = '_';
1006 return SaveName;
1010 int game::GetMoveCommandKeyBetweenPoints (v2 A, v2 B) {
1011 for (int c = 0; c < EXTENDED_DIRECTION_COMMAND_KEYS; ++c) {
1012 if ((A + GetMoveVector(c)) == B) return GetMoveCommandKey(c);
1014 return DIR_ERROR;
1018 void game::ApplyDivineTick () {
1019 for (int c = 1; c <= GODS; ++c) GetGod(c)->ApplyDivineTick();
1023 void game::ApplyDivineAlignmentBonuses (god *CompareTarget, int Multiplier, truth Good) {
1024 for (int c = 1; c <= GODS; ++c) if (GetGod(c) != CompareTarget) GetGod(c)->AdjustRelation(CompareTarget, Multiplier, Good);
1028 v2 game::GetDirectionVectorForKey (int Key) {
1029 if (Key == KEY_NUMPAD_5 || Key == '.') return ZERO_V2; /* k8: '.' */
1030 for (int c = 0; c < EXTENDED_DIRECTION_COMMAND_KEYS; ++c) if (Key == GetMoveCommandKey(c)) return GetMoveVector(c);
1031 return ERROR_V2;
1035 double game::GetMinDifficulty () {
1036 double Base = CurrentLevel->GetDifficulty()*0.2;
1037 sLong MultiplierExponent = 0;
1038 ivantime Time;
1039 GetTime(Time);
1040 int Modifier = Time.Day-DANGER_PLUS_DAY_MIN;
1041 if (Modifier > 0) Base += DANGER_PLUS_MULTIPLIER * Modifier;
1042 for (;;) {
1043 int Dice = RAND()%25;
1044 if (Dice < 5 && MultiplierExponent > -3) {
1045 Base /= 3;
1046 --MultiplierExponent;
1047 continue;
1049 if (Dice >= 20 && MultiplierExponent < 3) {
1050 Base *= 3;
1051 ++MultiplierExponent;
1052 continue;
1054 return Base;
1059 void game::ShowLevelMessage () {
1060 if (CurrentLevel->GetLevelMessage().GetSize()) ADD_MESSAGE("%s", CurrentLevel->GetLevelMessage().CStr());
1061 CurrentLevel->SetLevelMessage("");
1065 int game::DirectionQuestion (cfestring &Topic, truth RequireAnswer, truth AcceptYourself) {
1066 for (;;) {
1067 int Key = AskForKeyPress(Topic);
1068 if (AcceptYourself && (Key == '.' || Key == KEY_NUMPAD_5)) return YOURSELF; //k8
1069 for (int c = 0; c < DIRECTION_COMMAND_KEYS; ++c) if (Key == GetMoveCommandKey(c)) return c;
1070 if (!RequireAnswer) return DIR_ERROR;
1075 void game::RemoveSaves (truth RealSavesAlso) {
1076 if (RealSavesAlso) {
1077 remove(festring(SaveName()+".sav").CStr());
1078 remove(festring(SaveName()+".wm").CStr());
1079 remove(festring(SaveName()+".png").CStr());
1080 remove(festring(SaveName()+".ipu").CStr());
1082 remove(festring(AutoSaveFileName+".sav").CStr());
1083 remove(festring(AutoSaveFileName+".wm").CStr());
1084 remove(festring(AutoSaveFileName+".png").CStr());
1085 remove(festring(AutoSaveFileName+".ipu").CStr());
1086 festring File;
1087 for (int i = 1; i < Dungeons; ++i) {
1088 for (int c = 0; c < GetDungeon(i)->GetLevels(); ++c) {
1089 /* This looks very odd. And it is very odd.
1090 * Indeed, gcc is very odd to not compile this correctly with -O3
1091 * if it is written in a less odd way. */
1092 File = SaveName()+'.'+i;
1093 File << c;
1094 if (RealSavesAlso) remove(File.CStr());
1095 File = AutoSaveFileName+'.'+i;
1096 File << c;
1097 remove(File.CStr());
1103 void game::SetPlayer (character *NP) {
1104 Player = NP;
1105 if (Player) Player->AddFlags(C_PLAYER);
1109 void game::InitDungeons () {
1110 Dungeons = *GetGameScript()->GetDungeons()+1;
1111 //fprintf(stderr, "dungeon count: %d\n", Dungeons);
1112 Dungeon = new dungeon *[Dungeons];
1113 Dungeon[0] = 0;
1114 for (int c = 1; c < Dungeons; ++c) {
1115 Dungeon[c] = new dungeon(c);
1116 Dungeon[c]->SetIndex(c);
1121 void game::DoEvilDeed (int Amount) {
1122 if (!Amount) return;
1123 for (int c = 1; c <= GODS; ++c) {
1124 int Change = Amount-Amount*GetGod(c)->GetAlignment()/5;
1125 if (!IsInWilderness() && Player->GetLSquareUnder()->GetDivineMaster() == c) {
1126 if (GetGod(c)->GetRelation()-(Change << 1) < -750) {
1127 if (GetGod(c)->GetRelation() > -750) GetGod(c)->SetRelation(-750);
1128 } else if (GetGod(c)->GetRelation()-(Change << 1) > 750) {
1129 if (GetGod(c)->GetRelation() < 750) GetGod(c)->SetRelation(750);
1130 } else GetGod(c)->SetRelation(GetGod(c)->GetRelation()-(Change << 1));
1131 } else {
1132 if(GetGod(c)->GetRelation()-Change < -500) {
1133 if (GetGod(c)->GetRelation() > -500) GetGod(c)->SetRelation(-500);
1134 } else if (GetGod(c)->GetRelation()-Change > 500) {
1135 if (GetGod(c)->GetRelation() < 500) GetGod(c)->SetRelation(500);
1136 } else GetGod(c)->SetRelation(GetGod(c)->GetRelation() - Change);
1142 void game::SaveWorldMap (cfestring &SaveName, truth DeleteAfterwards) {
1143 outputfile SaveFile(SaveName+".wm", ivanconfig::GetUseMaximumCompression());
1144 SaveFile << WorldMap;
1145 if (DeleteAfterwards) {
1146 delete WorldMap;
1147 WorldMap = 0;
1152 worldmap *game::LoadWorldMap (cfestring &SaveName) {
1153 inputfile SaveFile(SaveName+".wm");
1154 SaveFile >> WorldMap;
1155 return WorldMap;
1159 void game::Hostility (team *Attacker, team *Defender) {
1160 for (int c = 0; c < Teams; ++c) {
1161 if (GetTeam(c) != Attacker && GetTeam(c) != Defender &&
1162 GetTeam(c)->GetRelation(Defender) == FRIEND &&
1163 c != NEW_ATTNAM_TEAM && c != TOURIST_GUIDE_TEAM) // gum solution
1164 GetTeam(c)->SetRelation(Attacker, HOSTILE);
1169 void game::CreateTeams () {
1170 Teams = *GetGameScript()->GetTeams();
1171 //fprintf(stderr, "team count: %d\n", Teams);
1172 Team = new team*[Teams];
1173 for (int c = 0; c < Teams; ++c) {
1174 Team[c] = new team(c);
1175 for (int i = 0; i < c; ++i) Team[i]->SetRelation(Team[c], UNCARING);
1177 for (int c = 0; c < Teams; ++c) if (c != MONSTER_TEAM) Team[MONSTER_TEAM]->SetRelation(Team[c], HOSTILE);
1178 const std::list<std::pair<int, teamscript> >& TeamScript = GetGameScript()->GetTeam();
1179 for (std::list<std::pair<int, teamscript> >::const_iterator i = TeamScript.begin(); i != TeamScript.end(); ++i) {
1180 for (uInt c = 0; c < i->second.GetRelation().size(); ++c) {
1181 GetTeam(i->second.GetRelation()[c].first)->SetRelation(GetTeam(i->first), i->second.GetRelation()[c].second);
1183 cint *KillEvilness = i->second.GetKillEvilness();
1184 if (KillEvilness) GetTeam(i->first)->SetKillEvilness(*KillEvilness);
1185 if (i->second.GetName()) GetTeam(i->first)->SetName(*i->second.GetName());
1190 team *game::FindTeam (cfestring &name) {
1191 for (int c = 0; c < Teams; ++c) {
1192 if (Team[c]->GetName().CompareIgnoreCase(name) == 0) return Team[c];
1194 return 0;
1198 /* v2 Pos should be removed from xxxQuestion()s? */
1199 festring game::StringQuestion (cfestring &Topic, col16 Color, festring::sizetype MinLetters, festring::sizetype MaxLetters, truth AllowExit, stringkeyhandler KeyHandler) {
1200 DrawEverythingNoBlit();
1201 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23)); // pos may be incorrect!
1202 festring Return;
1203 iosystem::StringQuestion(Return, Topic, v2(16, 6), Color, MinLetters, MaxLetters, false, AllowExit, KeyHandler);
1204 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1205 return Return;
1209 sLong game::NumberQuestion (cfestring &Topic, col16 Color, truth ReturnZeroOnEsc) {
1210 DrawEverythingNoBlit();
1211 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1212 sLong Return = iosystem::NumberQuestion(Topic, v2(16, 6), Color, false, ReturnZeroOnEsc);
1213 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1214 return Return;
1218 sLong game::ScrollBarQuestion (cfestring &Topic, sLong BeginValue, sLong Step, sLong Min, sLong Max, sLong AbortValue, col16 TopicColor, col16 Color1, col16 Color2, void (*Handler)(sLong)) {
1219 DrawEverythingNoBlit();
1220 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1221 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);
1222 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1223 return Return;
1227 feuLong game::IncreaseLOSTick () {
1228 if (LOSTick != 0xFE) return LOSTick += 2;
1229 CurrentLevel->InitLastSeen();
1230 return LOSTick = 4;
1234 void game::UpdateCamera () {
1235 UpdateCameraX();
1236 UpdateCameraY();
1240 truth game::HandleQuitMessage () {
1241 if (IsRunning()) {
1242 if (IsInGetCommand()) {
1243 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)) {
1244 case 0:
1245 Save();
1246 RemoveSaves(false);
1247 break;
1248 case 2:
1249 GetCurrentArea()->SendNewDrawRequest();
1250 DrawEverything();
1251 return false;
1252 default:
1253 festring Msg = CONST_S("cowardly quit the game");
1254 Player->AddScoreEntry(Msg, 0.75);
1255 End(Msg, true, false);
1256 break;
1258 } 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)) {
1259 RemoveSaves();
1260 } else {
1261 GetCurrentArea()->SendNewDrawRequest();
1262 DrawEverything();
1263 return false;
1266 return true;
1270 int game::GetDirectionForVector (v2 Vector) {
1271 for (int c = 0; c < DIRECTION_COMMAND_KEYS; ++c) if (Vector == GetMoveVector(c)) return c;
1272 return DIR_ERROR;
1276 cchar *game::GetVerbalPlayerAlignment () {
1277 sLong Sum = 0;
1278 for (int c = 1; c <= GODS; ++c) {
1279 if (GetGod(c)->GetRelation() > 0) Sum += GetGod(c)->GetRelation() * (5 - GetGod(c)->GetAlignment());
1281 if (Sum > 15000) return "extremely lawful";
1282 if (Sum > 10000) return "very lawful";
1283 if (Sum > 5000) return "lawful";
1284 if (Sum > 1000) return "mildly lawful";
1285 if (Sum > -1000) return "neutral";
1286 if (Sum > -5000) return "mildly chaotic";
1287 if (Sum > -10000) return "chaotic";
1288 if (Sum > -15000) return "very chaotic";
1289 return "extremely chaotic";
1293 void game::CreateGods () {
1294 God = new god*[GODS+1];
1295 God[0] = 0;
1296 for (int c = 1; c < protocontainer<god>::GetSize(); ++c) {
1297 auto proto = protocontainer<god>::GetProto(c);
1298 if (!proto) ABORT("God #%d is not defined!", c);
1299 God[c] = proto->Spawn();
1304 void game::BusyAnimation () {
1305 BusyAnimation(DOUBLE_BUFFER, false);
1309 void game::BusyAnimation (bitmap *Buffer, truth ForceDraw) {
1310 static clock_t LastTime = 0;
1311 static int Frame = 0;
1312 static blitdata B1 = {
1314 { 0, 0 },
1315 { 0, 0 },
1316 { RES.X, RES.Y },
1317 { 0 },
1321 static blitdata B2 = {
1323 { 0, 0 },
1324 { (RES.X >> 1) - 100, (RES.Y << 1) / 3 - 100 },
1325 { 200, 200 },
1326 { 0 },
1330 if (ForceDraw || clock()-LastTime > CLOCKS_PER_SEC/25) {
1331 B2.Bitmap = Buffer;
1332 B2.Dest.X = (RES.X>>1)-100+EnterTextDisplacement.X;
1333 B2.Dest.Y = (RES.Y<<1)/3-100+EnterTextDisplacement.Y;
1334 if (EnterImage) {
1335 B1.Bitmap = Buffer;
1336 EnterImage->NormalMaskedBlit(B1);
1338 BusyAnimationCache[Frame]->NormalBlit(B2);
1339 if (Buffer == DOUBLE_BUFFER) graphics::BlitDBToScreen();
1340 if (++Frame == 32) Frame = 0;
1341 LastTime = clock();
1346 void game::CreateBusyAnimationCache () {
1347 bitmap Elpuri(TILE_V2, TRANSPARENT_COLOR);
1348 Elpuri.ActivateFastFlag();
1349 packcol16 Color = MakeRGB16(60, 60, 60);
1350 igraph::GetCharacterRawGraphic()->MaskedBlit(&Elpuri, v2(64, 0), ZERO_V2, TILE_V2, &Color);
1351 bitmap Circle(v2(200, 200), TRANSPARENT_COLOR);
1352 Circle.ActivateFastFlag();
1353 for (int x = 0; x < 4; ++x) Circle.DrawPolygon(100, 100, 95+x, 50, MakeRGB16(255-12*x, 0, 0));
1354 blitdata B1 = {
1356 { 0, 0 },
1357 { 92, 92 },
1358 { TILE_SIZE, TILE_SIZE },
1359 { 0 },
1360 TRANSPARENT_COLOR,
1363 blitdata B2 = {
1365 { 0, 0 },
1366 { 0, 0 },
1367 { 200, 200 },
1368 { 0 },
1369 TRANSPARENT_COLOR,
1372 for (int c = 0; c < 32; ++c) {
1373 B1.Bitmap = B2.Bitmap = BusyAnimationCache[c] = new bitmap(v2(200, 200), 0);
1374 B1.Bitmap->ActivateFastFlag();
1375 Elpuri.NormalMaskedBlit(B1);
1376 double Rotation = 0.3+c*FPI/80;
1377 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);
1378 Circle.NormalMaskedBlit(B2);
1383 int game::AskForKeyPress (cfestring &Topic) {
1384 DrawEverythingNoBlit();
1385 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Topic.CapitalizeCopy().CStr());
1386 graphics::BlitDBToScreen();
1387 int Key = GET_KEY();
1388 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1389 return Key;
1393 void game::AskForEscPress (cfestring &Topic) {
1394 DrawEverythingNoBlit();
1395 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), RED/*WHITE*/, "%s [press ESC]", Topic.CapitalizeCopy().CStr());
1396 graphics::BlitDBToScreen();
1397 int Key;
1398 do {
1399 Key = GET_KEY();
1400 } while (Key != KEY_ESC);
1401 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1405 /* Handler is called when the key has been identified as a movement key
1406 * KeyHandler is called when the key has NOT been identified as a movement key
1407 * Both can be deactivated by passing 0 as parameter */
1408 v2 game::PositionQuestion (cfestring &Topic, v2 CursorPos, void (*Handler)(v2), positionkeyhandler KeyHandler, truth Zoom) {
1409 int Key = 0;
1410 SetDoZoom(Zoom);
1411 v2 Return;
1412 CursorData = RED_CURSOR;
1413 auto stpos = CursorPos;
1414 if (Handler) Handler(CursorPos);
1415 for (;;) {
1416 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1417 if (!Square->HasBeenSeen() &&
1418 (!Square->GetCharacter() || !Square->GetCharacter()->CanBeSeenByPlayer()) &&
1419 !GetSeeWholeMapCheatMode())
1421 DOUBLE_BUFFER->Fill(CalculateScreenCoordinates(CursorPos), TILE_V2, BLACK);
1422 } else {
1423 GetCurrentArea()->GetSquare(CursorPos)->SendStrongNewDrawRequest();
1426 if (Key == ' ' || Key == '.' || Key == KEY_NUMPAD_5) { Return = CursorPos; break; }
1427 if (Key == KEY_ESC) { Return = ERROR_V2; break; }
1429 v2 DirectionVector = GetDirectionVectorForKey(Key);
1430 if (DirectionVector != ERROR_V2) {
1431 CursorPos += DirectionVector;
1432 if (CursorPos.X > GetCurrentArea()->GetXSize()-1) CursorPos.X = 0;
1433 if (CursorPos.X < 0) CursorPos.X = GetCurrentArea()->GetXSize()-1;
1434 if (CursorPos.Y > GetCurrentArea()->GetYSize()-1) CursorPos.Y = 0;
1435 if (CursorPos.Y < 0) CursorPos.Y = GetCurrentArea()->GetYSize()-1;
1436 if (Handler) Handler(CursorPos);
1437 } else if (KeyHandler) {
1438 CursorPos = KeyHandler(CursorPos, Key);
1439 if (CursorPos == ERROR_V2 || CursorPos == ABORT_V2) {
1440 Return = CursorPos;
1441 break;
1443 // return back to start
1444 if (Key == 'H') {
1445 if (CursorPos != stpos) {
1446 CursorPos = stpos;
1447 if (Handler) Handler(CursorPos);
1452 if (ivanconfig::GetAutoCenterMapOnLook()) {
1453 UpdateCameraX(CursorPos.X);
1454 UpdateCameraY(CursorPos.Y);
1455 } else {
1456 if (CursorPos.X < GetCamera().X+3 || CursorPos.X >= GetCamera().X+GetScreenXSize()-3) UpdateCameraX(CursorPos.X);
1457 if (CursorPos.Y < GetCamera().Y+3 || CursorPos.Y >= GetCamera().Y+GetScreenYSize()-3) UpdateCameraY(CursorPos.Y);
1460 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Topic.CStr());
1461 SetCursorPos(CursorPos);
1462 DrawEverything();
1463 Key = GET_KEY();
1466 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1467 igraph::BlitBackGround(v2(RES.X-96, RES.Y-96), v2(80, 80));
1468 SetDoZoom(false);
1469 SetCursorPos(v2(-1, -1));
1470 return Return;
1474 void game::LookHandler (v2 CursorPos) {
1475 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1476 festring OldMemory;
1478 if (GetSeeWholeMapCheatMode()) {
1479 OldMemory = Square->GetMemorizedDescription();
1480 if (IsInWilderness()) GetWorldMap()->GetWSquare(CursorPos)->UpdateMemorizedDescription(true);
1481 else GetCurrentLevel()->GetLSquare(CursorPos)->UpdateMemorizedDescription(true);
1484 festring Msg;
1485 if (Square->HasBeenSeen() || GetSeeWholeMapCheatMode()) {
1486 if (!IsInWilderness() && !Square->CanBeSeenByPlayer() && GetCurrentLevel()->GetLSquare(CursorPos)->CanBeFeltByPlayer())
1487 Msg = CONST_S("You feel here ");
1488 else if (Square->CanBeSeenByPlayer(true) || GetSeeWholeMapCheatMode())
1489 Msg = CONST_S("You see here ");
1490 else
1491 Msg = CONST_S("You remember here ");
1492 Msg << Square->GetMemorizedDescription() << '.';
1493 if (!IsInWilderness() && (Square->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) {
1494 lsquare *LSquare = GetCurrentLevel()->GetLSquare(CursorPos);
1495 LSquare->DisplaySmokeInfo(Msg);
1496 if (LSquare->HasEngravings() && LSquare->IsTransparent()) {
1497 if (LSquare->EngravingsCanBeReadByPlayer() || GetSeeWholeMapCheatMode()) LSquare->DisplayEngravedInfo(Msg);
1498 else Msg << " Something has been engraved here.";
1501 } else Msg = CONST_S("You have never been here.");
1502 character *Character = Square->GetCharacter();
1503 if (Character && (Character->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) Character->DisplayInfo(Msg);
1504 if (!(RAND()%10000) && (Square->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) Msg << " You see here a frog eating a magnolia.";
1505 ADD_MESSAGE("%s", Msg.CStr());
1506 if (GetSeeWholeMapCheatMode()) Square->SetMemorizedDescription(OldMemory);
1510 truth game::AnimationController () {
1511 DrawEverythingNoBlit(true);
1512 return true;
1516 void game::LoadGlobalValueMap (TextInput &fl) {
1517 festring word;
1518 fl.setGetVarCB(game::ldrGetVar);
1519 for (fl.ReadWord(word, false); !fl.Eof(); fl.ReadWord(word, false)) {
1520 if (word == "Include") {
1521 word = fl.ReadWord();
1522 if (fl.ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1523 //fprintf(stderr, "loading: %s\n", word.CStr());
1524 TextInputFile incf(inputfile::buildIncludeName(fl.GetFileName(), word), &game::GetGlobalValueMap());
1525 LoadGlobalValueMap(incf);
1526 continue;
1528 if (word == "Message") {
1529 word = fl.ReadWord();
1530 if (fl.ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1531 fprintf(stderr, "MESSAGE: %s\n", word.CStr());
1532 continue;
1534 if (word != "#") ABORT("Illegal datafile define in file %s on line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1535 fl.ReadWord(word, true);
1536 if (word == "enum" || word == "bitenum") {
1537 truth isBit = (word == "bitenum");
1538 sLong idx = 0;
1539 fl.ReadWord(word, true);
1540 // check for named enum
1541 festring enumName = "";
1542 if (enumName.GetSize()) ABORT("The thing that should not be");
1543 if (word == "#") {
1544 // get enum name
1545 fl.ReadWord(word, true);
1546 enumName = (isBit ? "# bitenum # " : "# enum # ");
1547 enumName << word;
1548 // get starting index
1549 idx = game::FindGlobalValue(enumName, 0);
1550 // get bracket
1551 fl.ReadWord(word, true);
1552 //fprintf(stderr, "named enum '%s'; idx=%d\n", enumName.CStr(), idx);
1554 if (word != "{") ABORT("'{' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1555 festring idName;
1556 truth done = false;
1557 while (!done) {
1558 truth forcedIndex = false;
1559 sLong idxnew = 0;
1560 truth doAdvance = true;
1561 // read name
1562 fl.ReadWord(word, true);
1563 if (word == "}") break;
1564 if (word == "=" || word == ":=") {
1565 // nameless
1566 idName.Empty();
1567 } else {
1568 idName = word;
1569 fl.ReadWord(word, true);
1571 if (word == "=") {
1572 // set current index
1573 idxnew = fl.ReadNumber();
1574 forcedIndex = true;
1575 doAdvance = true; // just in case
1576 //fprintf(stderr, "force index for `%s`(%s): %d (idx=%d)\n", idName.CStr(), (idName.GetSize() == 0 ? "empty" : "non-empty"), idxnew, idx);
1577 } else if (word == ":=") {
1578 idxnew = fl.ReadNumber();
1579 forcedIndex = true;
1580 doAdvance = false;
1581 //fprintf(stderr, "enum; %s := %d!\n", idName.CStr(), idxnew);
1582 } else {
1583 if (word != "," && word != ";" && word != "}") ABORT("',' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1584 if (word == "}") done = true;
1585 forcedIndex = false;
1586 doAdvance = true; // just in case
1588 if (idName.GetSize() && HasGlobalValue(idName)) ABORT("duplicate global '%s' in file %s at line %d!", idName.CStr(), fl.GetFileName().CStr(), fl.TokenLine());
1589 if (forcedIndex) {
1590 if (isBit) {
1591 // for bitsets, nameless "=" forces new bit index, otherwise index isn't changed, and taken as is (without shifts)
1592 if (idName.GetSize()) {
1593 GlobalValueMap.insert(std::make_pair(idName, idxnew));
1594 doAdvance = false;
1596 } else {
1597 // for enums, index is always forced, but not increased for nameless "="
1598 if (idName.GetSize()) {
1599 GlobalValueMap.insert(std::make_pair(idName, idxnew));
1600 if (doAdvance) ++idxnew;
1603 if (doAdvance) idx = idxnew;
1604 } else {
1605 // no forced index
1606 if (idName.GetSize() == 0) ABORT("The thing that should not be");
1607 if (!doAdvance) ABORT("The thing that should not be");
1608 sLong i = idx;
1609 if (isBit) i = 1<<i;
1610 GlobalValueMap.insert(std::make_pair(idName, i));
1611 if (doAdvance) ++idx; // advance index
1614 fl.skipBlanks();
1615 int ch = fl.GetChar();
1616 if (ch != EOF && ch != ';') fl.UngetChar(ch);
1617 //if (fl.ReadWord() != ";") ABORT("';' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1618 // save current enum index, so we can continue later
1619 if (enumName.GetSize()) {
1620 //fprintf(stderr, "named enum '%s' ends with idx=%d\n", enumName.CStr(), idx);
1621 GlobalValueMap.insert(std::make_pair(enumName, idx));
1623 continue;
1625 if (word == "define") {
1626 fl.ReadWord(word);
1627 if (word.GetSize() == 0) ABORT("The thing that should not be");
1628 if (HasGlobalValue(word)) ABORT("duplicate global '%s' in file %s at line %d!", word.CStr(), fl.GetFileName().CStr(), fl.TokenLine());
1629 sLong v = fl.ReadNumber();
1630 GlobalValueMap.insert(std::make_pair(word, v));
1631 continue;
1633 ABORT("Illegal datafile define in file %s on line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1638 truth game::HasGlobalValue (cfestring &name) {
1639 auto it = GlobalValueMap.find(name);
1640 return (it != GlobalValueMap.end());
1644 sLong game::FindGlobalValue (cfestring &name, sLong defval, truth* found) {
1645 auto it = GlobalValueMap.find(name);
1646 if (it != GlobalValueMap.end()) {
1647 if (found) *found = true;
1648 return it->second;
1649 } else {
1650 if (found) *found = false;
1651 return defval;
1656 sLong game::FindGlobalValue (cfestring &name, truth* found) {
1657 return FindGlobalValue(name, -1, found);
1661 // this will fail if there is no such constant
1662 //TODO: cache values
1663 sLong game::GetGlobalConst (cfestring &name) {
1664 auto it = GlobalValueMap.find(name);
1665 if (it == GlobalValueMap.end()) ABORT("Global constant '%s' not found!", name.CStr());
1666 return it->second;
1670 const std::vector<festring> &game::GetModuleList () { return mModuleList; }
1673 void game::LoadModuleList () {
1674 mModuleList.push_back("_default"); // always loaded
1675 TextInputFile ifl(GetGameDir()+"script/module.dat");
1676 LoadModuleListFile(ifl);
1680 void game::LoadModuleListFile (TextInput &fl) {
1681 festring word;
1682 fl.setGetVarCB(game::ldrGetVar);
1683 for (;;) {
1684 fl.ReadWord(word, false);
1685 if (fl.Eof()) break; // no more modules
1686 // messages
1687 if (word == "Message") {
1688 word = fl.ReadWord();
1689 if (fl.ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1690 fprintf(stderr, "MESSAGE: %s\n", word.CStr());
1691 continue;
1693 // includes
1694 if (word == "Include") {
1695 word = fl.ReadWord();
1696 if (fl.ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1697 //fprintf(stderr, "loading: %s\n", word.CStr());
1698 TextInputFile ifl(inputfile::buildIncludeName(fl.GetFileName(), word));
1699 LoadModuleListFile(ifl);
1700 continue;
1702 // module
1703 if (word != "Module") ABORT("`Module` expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1704 word = fl.ReadWord();
1705 if (word.GetSize() == 0) ABORT("Cannot register empty module (file %s at line %d)!", fl.GetFileName().CStr(), fl.TokenLine());
1706 if (fl.ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1707 // check for duplicates
1708 for (auto &modname : mModuleList) {
1709 if (modname == word) ABORT("Duplicate module '%s' in file %s at line %d!", word.CStr(), fl.GetFileName().CStr(), fl.TokenLine());
1711 // looks like valid moudle, remember it
1712 mModuleList.push_back(word);
1717 void game::InitGlobalValueMap () {
1718 TextInputFile SaveFile(GetGameDir()+"script/define.dat", &GlobalValueMap);
1719 LoadGlobalValueMap(SaveFile);
1720 // load defines from modules
1721 for (auto &modname : mModuleList) {
1722 festring infname = game::GetGameDir()+"script/"+modname+"/define.dat";
1723 if (inputfile::fileExists(infname)) {
1724 TextInputFile ifl(infname, &game::GetGlobalValueMap());
1725 LoadGlobalValueMap(ifl);
1731 void game::TextScreen (cfestring &Text, v2 Displacement, col16 Color, truth GKey, truth Fade, bitmapeditor BitmapEditor) {
1732 globalwindowhandler::DisableControlLoops();
1733 iosystem::TextScreen(Text, Displacement, Color, GKey, Fade, BitmapEditor);
1734 globalwindowhandler::EnableControlLoops();
1738 /* ... all the keys that are acceptable
1739 DefaultAnswer = REQUIRES_ANSWER if this question requires an answer
1740 Not surprisingly KeyNumber is the number of keys at ...
1742 int game::KeyQuestion (cfestring &Message, int DefaultAnswer, int KeyNumber, ...) {
1743 int keybuf[256];
1744 int *Key = keybuf;
1745 va_list Arguments;
1746 va_start(Arguments, KeyNumber);
1747 for (int c = 0; c < KeyNumber; ++c) {
1748 if (c > 255) ABORT("Too many keys in `game::KeyQuestion()`");
1749 Key[c] = va_arg(Arguments, int);
1751 va_end(Arguments);
1752 DrawEverythingNoBlit();
1753 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Message.CStr());
1754 auto cursave = graphics::getCursorState(16+Message.rawLength()*8, 8+7, true);
1755 int Return = 0;
1756 while (!Return) {
1757 int k = GET_KEY();
1758 for (int c = 0; c < KeyNumber; ++c) {
1759 if (Key[c] == k) {
1760 Return = k;
1761 break;
1764 if (!Return && DefaultAnswer != REQUIRES_ANSWER) Return = DefaultAnswer;
1766 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1767 return Return;
1771 v2 game::LookKeyHandler (v2 CursorPos, int Key) {
1772 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1773 switch (Key) {
1774 case 'i':
1775 if (!IsInWilderness()) {
1776 if (Square->CanBeSeenByPlayer() || CursorPos == Player->GetPos() || GetSeeWholeMapCheatMode()) {
1777 lsquare *LSquare = GetCurrentLevel()->GetLSquare(CursorPos);
1778 stack *Stack = LSquare->GetStack();
1779 if (LSquare->IsTransparent() && Stack->GetVisibleItems(Player))
1780 Stack->DrawContents(Player, "Items here", NO_SELECT|(GetSeeWholeMapCheatMode() ? 0 : NO_SPECIAL_INFO));
1781 else
1782 ADD_MESSAGE("You see no items here.");
1783 } else ADD_MESSAGE("You should perhaps move a bit closer.");
1785 break;
1786 case 'c':
1787 if (Square->CanBeSeenByPlayer() || CursorPos == Player->GetPos() || GetSeeWholeMapCheatMode()) {
1788 character *Char = Square->GetCharacter();
1789 if (Char && (Char->CanBeSeenByPlayer() || Char->IsPlayer() || GetSeeWholeMapCheatMode()))
1790 Char->PrintInfo();
1791 else
1792 ADD_MESSAGE("You see no one here.");
1793 } else ADD_MESSAGE("You should perhaps move a bit closer.");
1794 break;
1796 return CursorPos;
1800 v2 game::NameKeyHandler (v2 CursorPos, int Key) {
1801 if (SelectPet(Key)) return LastPetUnderCursor->GetPos();
1802 if (Key == 'n' || Key == 'N') {
1803 character *Char = GetCurrentArea()->GetSquare(CursorPos)->GetCharacter();
1804 if (Char && Char->CanBeSeenByPlayer()) Char->TryToName();
1805 else ADD_MESSAGE("You don't see anyone here to name.");
1807 return CursorPos;
1811 void game::End (festring DeathMessage, truth Permanently, truth AndGoToMenu) {
1812 if (!Permanently || WizardModeIsReallyActive()) game::Save();
1813 pool::AbortBe();
1814 globalwindowhandler::DeInstallControlLoop(AnimationController);
1815 SetIsRunning(false);
1816 if (Permanently || !WizardModeIsReallyActive()) RemoveSaves(Permanently);
1817 if (Permanently && !WizardModeIsReallyActive()) {
1818 highscore HScore;
1819 if (HScore.LastAddFailed()) {
1820 iosystem::TextScreen(CONST_S("You didn't manage to get onto the high score list.\n\n\n\n")+GetPlayerName()+", "+DeathMessage+"\nRIP");
1821 } else {
1822 HScore.Draw();
1825 if (AndGoToMenu) {
1826 /* This prevents monster movement etc. after death. */
1827 throw quitrequest();
1832 int game::CalculateRoughDirection (v2 Vector) {
1833 if (!Vector.X && !Vector.Y) return YOURSELF;
1834 double Angle = femath::CalculateAngle(Vector);
1835 if (Angle < FPI / 8) return 4;
1836 else if (Angle < 3*FPI/8) return 7;
1837 else if (Angle < 5*FPI/8) return 6;
1838 else if (Angle < 7*FPI/8) return 5;
1839 else if (Angle < 9*FPI/8) return 3;
1840 else if (Angle < 11*FPI/8) return 0;
1841 else if (Angle < 13*FPI/8) return 1;
1842 else if (Angle < 15*FPI/8) return 2;
1843 else return 4;
1847 int game::Menu (bitmap *BackGround, v2 Pos, cfestring &Topic, cfestring &sMS, col16 Color, cfestring &SmallText1, cfestring &SmallText2) {
1848 globalwindowhandler::DisableControlLoops();
1849 int Return = iosystem::Menu(BackGround, Pos, Topic, sMS, Color, SmallText1, SmallText2);
1850 globalwindowhandler::EnableControlLoops();
1851 return Return;
1855 void game::InitDangerMap () {
1856 truth First = true;
1857 pool::RegisterState(false);
1858 //fprintf(stderr, "game::InitDangerMap(): START\n");
1859 for (int c1 = 1; c1 < protocontainer<character>::GetSize(); ++c1) {
1860 BusyAnimation();
1861 const character::prototype *Proto = protocontainer<character>::GetProto(c1);
1862 if (!Proto) continue; // missing character
1863 const character::database *const *ConfigData = Proto->GetConfigData();
1864 int ConfigSize = Proto->GetConfigSize();
1865 for (int c2 = 0; c2 < ConfigSize; ++c2) {
1866 if (!ConfigData[c2]->IsAbstract) {
1867 int Config = ConfigData[c2]->Config;
1868 if (First) {
1869 NextDangerIDType = c1;
1870 NextDangerIDConfigIndex = c2;
1871 First = false;
1873 character *Char = Proto->Spawn(Config, NO_EQUIPMENT|NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1874 double NakedDanger = Char->GetRelativeDanger(Player, true);
1875 //fprintf(stderr, " game::InitDangerMap(): 00\n");
1876 delete Char;
1877 //Char->SendToHell(); // k8: equipment
1878 Char = Proto->Spawn(Config, NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1879 double EquippedDanger = Char->GetRelativeDanger(Player, true);
1880 //fprintf(stderr, " game::InitDangerMap(): 01\n");
1881 delete Char;
1882 //Char->SendToHell(); // k8: equipment
1883 DangerMap[configid(c1, Config)] = dangerid(NakedDanger, EquippedDanger);
1887 pool::RegisterState(true);
1888 //fprintf(stderr, "game::InitDangerMap(): DONE\n");
1892 void game::CalculateNextDanger () {
1893 if (IsInWilderness() || !*CurrentLevel->GetLevelScript()->GenerateMonsters()) return;
1894 const character::prototype *Proto = protocontainer<character>::GetProto(NextDangerIDType);
1895 if (!Proto) ABORT("Oops! game::CalculateNextDanger() failed!"); // missing character
1896 const character::database *const *ConfigData = Proto->GetConfigData();
1897 const character::database *DataBase = ConfigData[NextDangerIDConfigIndex];
1898 dangermap::iterator DangerIterator = DangerMap.find(configid(NextDangerIDType, DataBase->Config));
1899 team *Team = GetTeam(PLAYER_TEAM);
1900 if (DataBase && DangerIterator != DangerMap.end()) {
1901 //fprintf(stderr, "game::CalculateNextDanger(): START\n");
1902 pool::RegisterState(false);
1903 character *Char = Proto->Spawn(DataBase->Config, NO_EQUIPMENT|NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1904 std::list<character*>::const_iterator i;
1905 double DangerSum = Player->GetRelativeDanger(Char, true);
1906 for (i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i) {
1907 if ((*i)->IsEnabled() && !(*i)->IsTemporary() && !RAND_N(10)) DangerSum += (*i)->GetRelativeDanger(Char, true)/4;
1909 double CurrentDanger = 1/DangerSum;
1910 double NakedDanger = DangerIterator->second.NakedDanger;
1911 //fprintf(stderr, " game::CalculateNextDanger(): 00\n");
1912 delete Char;
1913 //Char->SendToHell(); // k8: equipment
1914 if (NakedDanger > CurrentDanger) DangerIterator->second.NakedDanger = (NakedDanger*9+CurrentDanger)/10;
1915 Char = Proto->Spawn(DataBase->Config, NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1916 DangerSum = Player->GetRelativeDanger(Char, true);
1917 for (i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i) {
1918 if ((*i)->IsEnabled() && !(*i)->IsTemporary() && !RAND_N(10)) DangerSum += (*i)->GetRelativeDanger(Char, true) / 4;
1920 CurrentDanger = 1/DangerSum;
1921 double EquippedDanger = DangerIterator->second.EquippedDanger;
1922 //fprintf(stderr, " game::CalculateNextDanger(): 01\n");
1923 delete Char;
1924 //Char->SendToHell(); // k8: equipment
1925 pool::RegisterState(true);
1926 if (EquippedDanger > CurrentDanger) DangerIterator->second.EquippedDanger = (EquippedDanger*9+CurrentDanger)/10;
1927 if (++NextDangerIDConfigIndex < Proto->GetConfigSize()) {
1928 //fprintf(stderr, "game::CalculateNextDanger(): EXIT0\n");
1929 return;
1931 int loopCount = 0;
1932 for (;;) {
1933 if (++NextDangerIDType >= protocontainer<character>::GetSize()) {
1934 NextDangerIDType = 1;
1935 if (++loopCount > 2) ABORT("Oops! Cannot calculate next danger!");
1937 Proto = protocontainer<character>::GetProto(NextDangerIDType);
1938 if (!Proto) continue; // missing character
1939 ConfigData = Proto->GetConfigData();
1940 int ConfigSize = Proto->GetConfigSize();
1941 for (int c = 0; c < ConfigSize; ++c) {
1942 if (!ConfigData[c]->IsAbstract) {
1943 NextDangerIDConfigIndex = c;
1944 //fprintf(stderr, "game::CalculateNextDanger(): EXIT1\n");
1945 return;
1949 //fprintf(stderr, "game::CalculateNextDanger(): DONE\n");
1950 } else {
1951 ABORT("It is dangerous to go ice fishing in the summer.");
1956 truth game::TryTravel (int Dungeon, int Area, int EntryIndex, truth AllowHostiles, truth AlliesFollow) {
1957 //fprintf(stderr, "game::TryTravel: Dungeon=%d; Area=%d; EntryIndex=%d\n", Dungeon, Area, EntryIndex);
1958 charactervector Group;
1959 if (LeaveArea(Group, AllowHostiles, AlliesFollow)) {
1960 CurrentDungeonIndex = Dungeon;
1961 EnterArea(Group, Area, EntryIndex);
1962 return true;
1964 return false;
1968 truth game::LeaveArea (charactervector &Group, truth AllowHostiles, truth AlliesFollow) {
1969 if (!IsInWilderness()) {
1970 if (AlliesFollow && !GetCurrentLevel()->CollectCreatures(Group, Player, AllowHostiles)) return false;
1971 Player->Remove();
1972 GetCurrentDungeon()->SaveLevel(SaveName(), CurrentLevelIndex);
1973 } else {
1974 Player->Remove();
1975 GetWorldMap()->GetPlayerGroup().swap(Group);
1976 SaveWorldMap();
1978 return true;
1982 /* Used always when the player enters an area. */
1983 void game::EnterArea (charactervector &Group, int Area, int EntryIndex) {
1984 if (Area != WORLD_MAP) {
1985 Generating = true;
1986 SetIsInWilderness(false);
1987 CurrentLevelIndex = Area;
1988 truth New = !PrepareRandomBone(Area) && !GetCurrentDungeon()->PrepareLevel(Area);
1989 igraph::CreateBackGround(*CurrentLevel->GetLevelScript()->GetBackGroundType());
1990 GetCurrentArea()->SendNewDrawRequest();
1991 v2 Pos = GetCurrentLevel()->GetEntryPos(Player, EntryIndex);
1992 if (Player) {
1993 GetCurrentLevel()->GetLSquare(Pos)->KickAnyoneStandingHereAway();
1994 Player->PutToOrNear(Pos);
1995 } else {
1996 SetPlayer(GetCurrentLevel()->GetLSquare(Pos)->GetCharacter());
1998 uInt c;
1999 for (c = 0; c < Group.size(); ++c) {
2000 v2 NPCPos = GetCurrentLevel()->GetNearestFreeSquare(Group[c], Pos);
2001 if (NPCPos == ERROR_V2) NPCPos = GetCurrentLevel()->GetRandomSquare(Group[c]);
2002 Group[c]->PutTo(NPCPos);
2004 GetCurrentLevel()->FiatLux();
2005 ctruth *AutoReveal = GetCurrentLevel()->GetLevelScript()->AutoReveal();
2006 if (New && AutoReveal && *AutoReveal) GetCurrentLevel()->Reveal();
2007 ShowLevelMessage();
2008 SendLOSUpdateRequest();
2009 UpdateCamera();
2011 /* Gum solution! */
2012 if (New && CurrentDungeonIndex == ATTNAM && Area == 0) {
2013 GlobalRainLiquid = powder::Spawn(SNOW);
2014 GlobalRainSpeed = v2(-64, 128);
2015 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
2018 if (New && CurrentDungeonIndex == NEW_ATTNAM && Area == 0) {
2019 GlobalRainLiquid = liquid::Spawn(WATER);
2020 GlobalRainSpeed = v2(256, 512);
2021 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
2024 if (New && CurrentDungeonIndex == ELPURI_CAVE && Area == OREE_LAIR) {
2025 GlobalRainLiquid = liquid::Spawn(BLOOD);
2026 GlobalRainSpeed = v2(256, 512);
2027 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
2028 GlobalRainLiquid->SetVolumeNoSignals(200);
2029 CurrentLevel->EnableGlobalRain();
2032 if (New && CurrentDungeonIndex == MUNTUO && Area == 0) {
2033 GlobalRainLiquid = liquid::Spawn(WATER);
2034 GlobalRainSpeed = v2(-64, 1024);
2035 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
2038 Generating = false;
2039 GetCurrentLevel()->UpdateLOS();
2040 Player->SignalStepFrom(0);
2042 for (c = 0; c < Group.size(); ++c) Group[c]->SignalStepFrom(0);
2044 if (ivanconfig::GetAutoSaveInterval()) Save(GetAutoSaveFileName().CStr());
2045 } else {
2046 igraph::CreateBackGround(GRAY_FRACTAL);
2047 SetIsInWilderness(true);
2048 LoadWorldMap();
2049 SetCurrentArea(WorldMap);
2050 CurrentWSquareMap = WorldMap->GetMap();
2051 GetWorldMap()->GetPlayerGroup().swap(Group);
2052 //fprintf(stderr, "EINDEX=%d; pos=(%d,%d)\n", EntryIndex, GetWorldMap()->GetEntryPos(Player, EntryIndex).X, GetWorldMap()->GetEntryPos(Player, EntryIndex).Y);
2053 Player->PutTo(GetWorldMap()->GetEntryPos(Player, EntryIndex));
2054 SendLOSUpdateRequest();
2055 UpdateCamera();
2056 GetWorldMap()->UpdateLOS();
2057 if (ivanconfig::GetAutoSaveInterval()) Save(GetAutoSaveFileName().CStr());
2062 int game::CompareLightToInt (col24 L, col24 Int) {
2063 if ((L & 0xFF0000) > Int || (L & 0xFF00) > Int || (L & 0xFF) > Int) return 1;
2064 if ((L & 0xFF0000) == Int || (L & 0xFF00) == Int || (L & 0xFF) == Int) return 0;
2065 return -1;
2069 void game::SetStandardListAttributes (felist &List) {
2070 List.SetPos(v2(26, 42));
2071 List.SetWidth(652);
2072 List.SetFlags(DRAW_BACKGROUND_AFTERWARDS);
2073 List.SetUpKey(GetMoveCommandKey(KEY_UP_INDEX));
2074 List.SetDownKey(GetMoveCommandKey(KEY_DOWN_INDEX));
2078 void game::InitPlayerAttributeAverage () {
2079 AveragePlayerArmStrengthExperience
2080 = AveragePlayerLegStrengthExperience
2081 = AveragePlayerDexterityExperience
2082 = AveragePlayerAgilityExperience
2083 = 0;
2085 if (!Player->IsHumanoid()) return;
2087 humanoid *Player = static_cast<humanoid*>(GetPlayer());
2088 int Arms = 0;
2089 int Legs = 0;
2090 arm *RightArm = Player->GetRightArm();
2092 if (RightArm && !RightArm->UseMaterialAttributes()) {
2093 AveragePlayerArmStrengthExperience += RightArm->GetStrengthExperience();
2094 AveragePlayerDexterityExperience += RightArm->GetDexterityExperience();
2095 ++Arms;
2098 arm *LeftArm = Player->GetLeftArm();
2100 if (LeftArm && !LeftArm->UseMaterialAttributes()) {
2101 AveragePlayerArmStrengthExperience += LeftArm->GetStrengthExperience();
2102 AveragePlayerDexterityExperience += LeftArm->GetDexterityExperience();
2103 ++Arms;
2106 leg *RightLeg = Player->GetRightLeg();
2108 if (RightLeg && !RightLeg->UseMaterialAttributes()) {
2109 AveragePlayerLegStrengthExperience += RightLeg->GetStrengthExperience();
2110 AveragePlayerAgilityExperience += RightLeg->GetAgilityExperience();
2111 ++Legs;
2114 leg *LeftLeg = Player->GetLeftLeg();
2116 if (LeftLeg && !LeftLeg->UseMaterialAttributes()) {
2117 AveragePlayerLegStrengthExperience += LeftLeg->GetStrengthExperience();
2118 AveragePlayerAgilityExperience += LeftLeg->GetAgilityExperience();
2119 ++Legs;
2122 if (Arms) {
2123 AveragePlayerArmStrengthExperience /= Arms;
2124 AveragePlayerDexterityExperience /= Arms;
2127 if (Legs) {
2128 AveragePlayerLegStrengthExperience /= Legs;
2129 AveragePlayerAgilityExperience /= Legs;
2134 void game::UpdatePlayerAttributeAverage () {
2135 if (!Player->IsHumanoid()) return;
2137 humanoid *Player = static_cast<humanoid*>(GetPlayer());
2138 double PlayerArmStrengthExperience = 0;
2139 double PlayerLegStrengthExperience = 0;
2140 double PlayerDexterityExperience = 0;
2141 double PlayerAgilityExperience = 0;
2142 int Arms = 0;
2143 int Legs = 0;
2144 arm *RightArm = Player->GetRightArm();
2146 if (RightArm && !RightArm->UseMaterialAttributes()) {
2147 PlayerArmStrengthExperience += RightArm->GetStrengthExperience();
2148 PlayerDexterityExperience += RightArm->GetDexterityExperience();
2149 ++Arms;
2152 arm *LeftArm = Player->GetLeftArm();
2154 if (LeftArm && !LeftArm->UseMaterialAttributes()) {
2155 PlayerArmStrengthExperience += LeftArm->GetStrengthExperience();
2156 PlayerDexterityExperience += LeftArm->GetDexterityExperience();
2157 ++Arms;
2160 leg *RightLeg = Player->GetRightLeg();
2162 if (RightLeg && !RightLeg->UseMaterialAttributes()) {
2163 PlayerLegStrengthExperience += RightLeg->GetStrengthExperience();
2164 PlayerAgilityExperience += RightLeg->GetAgilityExperience();
2165 ++Legs;
2168 leg *LeftLeg = Player->GetLeftLeg();
2170 if (LeftLeg && !LeftLeg->UseMaterialAttributes()) {
2171 PlayerLegStrengthExperience += LeftLeg->GetStrengthExperience();
2172 PlayerAgilityExperience += LeftLeg->GetAgilityExperience();
2173 ++Legs;
2176 if (Arms) {
2177 AveragePlayerArmStrengthExperience = (49 * AveragePlayerArmStrengthExperience + PlayerArmStrengthExperience / Arms) / 50;
2178 AveragePlayerDexterityExperience = (49 * AveragePlayerDexterityExperience + PlayerDexterityExperience / Arms) / 50;
2181 if (Legs) {
2182 AveragePlayerLegStrengthExperience = (49 * AveragePlayerLegStrengthExperience + PlayerLegStrengthExperience / Legs) / 50;
2183 AveragePlayerAgilityExperience = (49 * AveragePlayerAgilityExperience + PlayerAgilityExperience / Legs) / 50;
2188 void game::CallForAttention (v2 Pos, int RangeSquare) {
2189 for (int c = 0; c < GetTeams(); ++c) {
2190 if (GetTeam(c)->HasEnemy())
2191 for (std::list<character*>::const_iterator i = GetTeam(c)->GetMember().begin(); i != GetTeam(c)->GetMember().end(); ++i)
2192 if ((*i)->IsEnabled()) {
2193 sLong ThisDistance = HypotSquare(sLong((*i)->GetPos().X) - Pos.X, sLong((*i)->GetPos().Y) - Pos.Y);
2194 if (ThisDistance <= RangeSquare && !(*i)->IsGoingSomeWhere()) (*i)->SetGoingTo(Pos);
2200 outputfile &operator << (outputfile &SaveFile, const homedata *HomeData) {
2201 if (HomeData) {
2202 SaveFile.Put(1);
2203 SaveFile << HomeData->Pos << HomeData->Dungeon << HomeData->Level << HomeData->Room;
2204 } else SaveFile.Put(0);
2205 return SaveFile;
2209 inputfile &operator >> (inputfile &SaveFile, homedata *&HomeData) {
2210 if (SaveFile.Get()) {
2211 HomeData = new homedata;
2212 SaveFile >> HomeData->Pos >> HomeData->Dungeon >> HomeData->Level >> HomeData->Room;
2214 return SaveFile;
2218 feuLong game::CreateNewCharacterID (character *NewChar) {
2219 feuLong ID = NextCharacterID++;
2220 /*k8:??? if(CharacterIDMap.find(ID) != CharacterIDMap.end())
2221 int esko = esko = 2;*/
2222 CharacterIDMap.insert(std::make_pair(ID, NewChar));
2223 return ID;
2227 feuLong game::CreateNewItemID (item *NewItem) {
2228 feuLong ID = NextItemID++;
2229 /*k8:??? if(ItemIDMap.find(ID) != ItemIDMap.end())
2230 int esko = esko = 2;*/
2231 if (NewItem) ItemIDMap.insert(std::make_pair(ID, NewItem));
2232 return ID;
2236 feuLong game::CreateNewTrapID (entity *NewTrap) {
2237 feuLong ID = NextTrapID++;
2238 /*k8:??? if(TrapIDMap.find(ID) != TrapIDMap.end())
2239 int esko = esko = 2;*/
2240 if (NewTrap) TrapIDMap.insert(std::make_pair(ID, NewTrap));
2241 return ID;
2245 character *game::SearchCharacter (feuLong ID) {
2246 characteridmap::iterator Iterator = CharacterIDMap.find(ID);
2247 return (Iterator != CharacterIDMap.end() ? Iterator->second : 0);
2251 item *game::SearchItem (feuLong ID) {
2252 itemidmap::iterator Iterator = ItemIDMap.find(ID);
2253 return (Iterator != ItemIDMap.end() ? Iterator->second : 0);
2257 entity *game::SearchTrap (feuLong ID) {
2258 trapidmap::iterator Iterator = TrapIDMap.find(ID);
2259 return (Iterator != TrapIDMap.end() ? Iterator->second : 0);
2263 outputfile &operator << (outputfile &SaveFile, const configid &Value) {
2264 SaveFile.Write(reinterpret_cast<cchar*>(&Value), sizeof(Value));
2265 return SaveFile;
2269 inputfile &operator >> (inputfile &SaveFile, configid &Value) {
2270 SaveFile.Read(reinterpret_cast<char*>(&Value), sizeof(Value));
2271 return SaveFile;
2275 outputfile &operator << (outputfile &SaveFile, const dangerid &Value) {
2276 SaveFile << Value.NakedDanger << Value.EquippedDanger;
2277 return SaveFile;
2281 inputfile &operator >> (inputfile &SaveFile, dangerid &Value) {
2282 SaveFile >> Value.NakedDanger >> Value.EquippedDanger;
2283 return SaveFile;
2287 /* The program can only create directories to the deepness of one, no more... */
2288 festring game::GetHomeDir () {
2289 festring Dir;
2290 Dir << getenv("HOME") << '/';
2291 return Dir;
2295 festring game::GetSavePath () {
2296 festring Dir;
2297 #ifdef LOCAL_SAVES
2298 Dir << ivanconfig::GetMyDir() << "/save/";
2299 #else
2300 Dir << getenv("HOME") << "/.ivan-save/";
2301 #endif
2302 return Dir;
2306 festring game::GetGameDir () {
2307 /*k8! return DATADIR "/ivan/"; */
2308 /*k8! return DATADIR "/"; */
2309 festring Dir;
2310 Dir << ivanconfig::GetMyDir() << "/";
2311 return Dir;
2315 festring game::GetBonePath () {
2316 /*k8! return LOCAL_STATE_DIR "/Bones/";*/
2317 festring Dir;
2318 #ifdef LOCAL_SAVES
2319 Dir << ivanconfig::GetMyDir() << "/save/bones/";
2320 #else
2321 Dir << getenv("HOME") << "/.ivan-save/bones/";
2322 #endif
2323 return Dir;
2327 level *game::GetLevel (int I) {
2328 return GetCurrentDungeon()->GetLevel(I);
2332 int game::GetLevels () {
2333 return GetCurrentDungeon()->GetLevels();
2337 void game::SignalDeath (ccharacter *Ghost, ccharacter *Murderer, festring DeathMsg) {
2338 if (InWilderness) DeathMsg << " in the world map";
2339 else DeathMsg << " in " << GetCurrentDungeon()->GetLevelDescription(CurrentLevelIndex);
2340 massacremap *MassacreMap;
2341 if (!Murderer) {
2342 ++MiscMassacreAmount;
2343 MassacreMap = &MiscMassacreMap;
2344 } else if(Murderer->IsPlayer()) {
2345 ++PlayerMassacreAmount;
2346 MassacreMap = &PlayerMassacreMap;
2347 } else if(Murderer->IsPet()) {
2348 ++PetMassacreAmount;
2349 MassacreMap = &PetMassacreMap;
2350 } else {
2351 ++MiscMassacreAmount;
2352 MassacreMap = &MiscMassacreMap;
2355 massacreid MI(Ghost->GetType(), Ghost->GetConfig(), Ghost->GetAssignedName());
2356 massacremap::iterator i = MassacreMap->find(MI);
2358 if (i == MassacreMap->end()) {
2359 i = MassacreMap->insert(std::make_pair(MI, killdata(1, Ghost->GetGenerationDanger()))).first;
2360 i->second.Reason.push_back(killreason(DeathMsg, 1));
2361 } else {
2362 ++i->second.Amount;
2363 i->second.DangerSum += Ghost->GetGenerationDanger();
2364 std::vector<killreason>& Reason = i->second.Reason;
2365 uInt c;
2366 for (c = 0; c < Reason.size(); ++c) {
2367 if (Reason[c].String == DeathMsg) {
2368 ++Reason[c].Amount;
2369 break;
2372 if (c == Reason.size()) Reason.push_back(killreason(DeathMsg, 1));
2377 void game::DisplayMassacreLists () {
2378 DisplayMassacreList(PlayerMassacreMap, "directly by you.", PlayerMassacreAmount);
2379 DisplayMassacreList(PetMassacreMap, "by your allies.", PetMassacreAmount);
2380 DisplayMassacreList(MiscMassacreMap, "by some other reason.", MiscMassacreAmount);
2384 struct massacresetentry {
2385 bool operator < (const massacresetentry &MSE) const { return festring::IgnoreCaseCompare(Key, MSE.Key); }
2386 festring Key;
2387 festring String;
2388 std::vector<festring> Details;
2389 int ImageKey;
2393 void game::DisplayMassacreList (const massacremap &MassacreMap, cchar *Reason, sLong Amount) {
2394 std::set<massacresetentry> MassacreSet;
2395 festring FirstPronoun;
2396 truth First = true;
2397 charactervector GraveYard;
2399 for (massacremap::const_iterator i1 = MassacreMap.begin(); i1 != MassacreMap.end(); ++i1) {
2400 auto monsProto = protocontainer<character>::GetProto(i1->first.Type);
2401 if (!monsProto) continue; // missing character
2402 character *Victim = monsProto->Spawn(i1->first.Config);
2403 Victim->SetAssignedName(i1->first.Name);
2404 massacresetentry Entry;
2405 GraveYard.push_back(Victim);
2406 Entry.ImageKey = AddToCharacterDrawVector(Victim);
2407 if (i1->second.Amount == 1) {
2408 Victim->AddName(Entry.Key, UNARTICLED);
2409 Victim->AddName(Entry.String, INDEFINITE);
2410 } else {
2411 Victim->AddName(Entry.Key, PLURAL);
2412 Entry.String << i1->second.Amount << ' ' << Entry.Key;
2414 if (First) {
2415 FirstPronoun = Victim->GetSex() == UNDEFINED ? "it" : Victim->GetSex() == MALE ? "he" : "she";
2416 First = false;
2418 const std::vector<killreason>& Reason = i1->second.Reason;
2419 std::vector<festring>& Details = Entry.Details;
2420 if (Reason.size() == 1) {
2421 festring Begin;
2422 if (Reason[0].Amount == 1) Begin = "";
2423 else if(Reason[0].Amount == 2) Begin = "both ";
2424 else Begin = "all ";
2425 Details.push_back(Begin + Reason[0].String);
2426 } else {
2427 for (uInt c = 0; c < Reason.size(); ++c) Details.push_back(CONST_S("")+Reason[c].Amount+' '+Reason[c].String);
2428 std::sort(Details.begin(), Details.end(), ignorecaseorderer());
2430 MassacreSet.insert(Entry);
2432 sLong Total = PlayerMassacreAmount+PetMassacreAmount+MiscMassacreAmount;
2433 festring MainTopic;
2434 if (Total == 1) MainTopic << "One creature perished during your adventure.";
2435 else MainTopic << Total << " creatures perished during your adventure.";
2436 felist List(MainTopic);
2437 SetStandardListAttributes(List);
2438 List.SetPageLength(15);
2439 List.AddFlags(SELECTABLE);
2440 List.SetEntryDrawer(CharacterEntryDrawer);
2441 List.AddDescription(CONST_S(""));
2442 festring SideTopic;
2443 if (Amount != Total) {
2444 SideTopic = CONST_S("The following ");
2445 if (Amount == 1) SideTopic << "one was killed " << Reason;
2446 else SideTopic << Amount << " were killed " << Reason;
2447 } else {
2448 if (Amount == 1) {
2449 FirstPronoun.Capitalize();
2450 SideTopic << FirstPronoun << " was killed " << Reason;
2451 } else SideTopic << "They were all killed " << Reason;
2453 List.AddDescription(SideTopic);
2454 List.AddDescription(CONST_S(""));
2455 List.AddDescription("Choose a type of creatures to browse death details.");
2456 std::set<massacresetentry>::const_iterator i2;
2457 for (i2 = MassacreSet.begin(); i2 != MassacreSet.end(); ++i2) List.AddEntry(i2->String, LIGHT_GRAY, 0, i2->ImageKey);
2458 for (;;) {
2459 int Chosen = List.Draw();
2460 if (Chosen & FELIST_ERROR_BIT) break;
2461 felist SubList(CONST_S("Massacre details"));
2462 SetStandardListAttributes(SubList);
2463 SubList.SetPageLength(20);
2464 int Counter = 0;
2465 for (i2 = MassacreSet.begin(); i2 != MassacreSet.end(); ++i2, ++Counter) {
2466 if (Counter == Chosen) {
2467 for (uInt c = 0; c < i2->Details.size(); ++c) SubList.AddEntry(i2->Details[c], LIGHT_GRAY);
2468 break;
2471 SubList.Draw();
2473 ClearCharacterDrawVector();
2474 for (uInt c = 0; c < GraveYard.size(); ++c) delete GraveYard[c];
2478 truth game::MassacreListsEmpty () {
2479 return PlayerMassacreMap.empty() && PetMassacreMap.empty() && MiscMassacreMap.empty();
2483 #ifdef WIZARD
2484 void game::SeeWholeMap () {
2485 if (SeeWholeMapCheatMode < 2) ++SeeWholeMapCheatMode; else SeeWholeMapCheatMode = 0;
2486 GetCurrentArea()->SendNewDrawRequest();
2488 #endif
2491 void game::CreateBone () {
2492 if (!WizardModeIsActive() && !IsInWilderness() && (RAND()&3) && GetCurrentLevel()->PreProcessForBone()) {
2493 int BoneIndex;
2494 festring BoneName;
2495 for (BoneIndex = 0; BoneIndex < 1000; ++BoneIndex) {
2496 BoneName = GetBonePath()+"bon_"+CurrentDungeonIndex+"_"+CurrentLevelIndex+"_"+BoneIndex;
2497 if (!inputfile::fileExists(BoneName)) break;
2499 if (BoneIndex != 1000) {
2500 //festring BoneName = GetBonePath()+"bon"+CurrentDungeonIndex+CurrentLevelIndex+BoneIndex;
2501 fprintf(stderr, "creating bone file: [%s]\n", BoneName.CStr());
2502 outputfile BoneFile(BoneName, true);
2503 BoneFile << int(BONE_FILE_VERSION) << PlayerName << CurrentLevel;
2509 truth game::PrepareRandomBone (int LevelIndex) {
2510 if (/*k8:WizardModeIsActive() ||*/ GetCurrentDungeon()->IsGenerated(LevelIndex) || !*GetCurrentDungeon()->GetLevelScript(LevelIndex)->CanGenerateBone()) return false;
2511 int BoneIndex;
2512 festring BoneName;
2513 for (BoneIndex = 0; BoneIndex < 1000; ++BoneIndex) {
2514 BoneName = GetBonePath()+"bon_"+CurrentDungeonIndex+"_"+LevelIndex+"_"+BoneIndex;
2515 inputfile BoneFile(BoneName, false);
2516 if (BoneFile.IsOpen() && !(RAND() & 7)) {
2517 if (ReadType(int, BoneFile) != BONE_FILE_VERSION) {
2518 BoneFile.Close();
2519 remove(BoneName.CStr());
2520 continue;
2522 festring Name;
2523 BoneFile >> Name;
2524 level *NewLevel = GetCurrentDungeon()->LoadLevel(BoneFile, LevelIndex);
2525 if (!NewLevel->PostProcessForBone()) {
2526 delete NewLevel;
2527 GetBoneItemIDMap().clear();
2528 GetBoneCharacterIDMap().clear();
2529 continue;
2531 NewLevel->FinalProcessForBone();
2532 GetBoneItemIDMap().clear();
2533 GetBoneCharacterIDMap().clear();
2534 SetCurrentArea(NewLevel);
2535 CurrentLevel = NewLevel;
2536 CurrentLSquareMap = NewLevel->GetMap();
2537 GetCurrentDungeon()->SetIsGenerated(LevelIndex, true);
2538 if (Name == PlayerName) ADD_MESSAGE("This place is oddly familiar. Like you had been here in one of your past lives.");
2539 else if (Player && Player->StateIsActivated(GAS_IMMUNITY)) ADD_MESSAGE("You feel the cool breeze of death.");
2540 else ADD_MESSAGE("You smell the stench of death.");
2541 break;
2544 Generating = true;
2545 if (BoneIndex != 1000) {
2546 remove(BoneName.CStr());
2547 return true;
2549 return false;
2553 double game::CalculateAverageDanger (const charactervector &EnemyVector, character *Char) {
2554 double DangerSum = 0;
2555 int Enemies = 0;
2556 for (uInt c = 0; c < EnemyVector.size(); ++c) {
2557 DangerSum += EnemyVector[c]->GetRelativeDanger(Char, true);
2558 ++Enemies;
2560 return DangerSum/Enemies;
2564 double game::CalculateAverageDangerOfAllNormalEnemies () {
2565 double DangerSum = 0;
2566 int Enemies = 0;
2567 for (int c1 = 1; c1 < protocontainer<character>::GetSize(); ++c1) {
2568 const character::prototype *Proto = protocontainer<character>::GetProto(c1);
2569 if (!Proto) continue; // missing character
2570 const character::database*const *ConfigData = Proto->GetConfigData();
2571 int ConfigSize = Proto->GetConfigSize();
2572 for (int c2 = 0; c2 < ConfigSize; ++c2) {
2573 if (!ConfigData[c2]->IsAbstract && !ConfigData[c2]->IsUnique && ConfigData[c2]->CanBeGenerated) {
2574 DangerSum += DangerMap.find(configid(c1, ConfigData[c2]->Config))->second.EquippedDanger;
2575 ++Enemies;
2579 return DangerSum/Enemies;
2583 character *game::CreateGhost () {
2584 double AverageDanger = CalculateAverageDangerOfAllNormalEnemies();
2585 charactervector EnemyVector;
2586 protosystem::CreateEveryNormalEnemy(EnemyVector);
2587 ghost *Ghost = ghost::Spawn();
2588 Ghost->SetTeam(GetTeam(MONSTER_TEAM));
2589 Ghost->SetGenerationDanger(CurrentLevel->GetDifficulty());
2590 Ghost->SetOwnerSoul(PlayerName);
2591 Ghost->SetIsActive(false);
2592 Ghost->EditAllAttributes(-4);
2593 Player->SetSoulID(Ghost->GetID());
2594 while (CalculateAverageDanger(EnemyVector, Ghost) > AverageDanger && Ghost->EditAllAttributes(1));
2595 for (uInt c = 0; c < EnemyVector.size(); ++c) delete EnemyVector[c];
2596 return Ghost;
2600 int game::GetMoveCommandKey (int I) {
2601 if (!ivanconfig::GetUseAlternativeKeys()) return MoveNormalCommandKey[I];
2602 return MoveAbnormalCommandKey[I];
2606 sLong game::GetScore () {
2607 double Counter = 0;
2608 massacremap::const_iterator i;
2609 massacremap SumMap = PlayerMassacreMap;
2610 for (i = PetMassacreMap.begin(); i != PetMassacreMap.end(); ++i) {
2611 killdata &KillData = SumMap[i->first];
2612 KillData.Amount += i->second.Amount;
2613 KillData.DangerSum += i->second.DangerSum;
2615 for (i = SumMap.begin(); i != SumMap.end(); ++i) {
2616 auto monsProto = protocontainer<character>::GetProto(i->first.Type);
2617 if (!monsProto) continue; // missing character
2618 character *Char = monsProto->Spawn(i->first.Config);
2619 int SumOfAttributes = Char->GetSumOfAttributes();
2620 Counter += sqrt(i->second.DangerSum / DEFAULT_GENERATION_DANGER) * SumOfAttributes * SumOfAttributes;
2621 delete Char;
2623 return sLong(0.01*Counter);
2627 /* Only works if New Attnam is loaded */
2628 truth game::TweraifIsFree () {
2629 for (std::list<character*>::const_iterator i = GetTeam(COLONIST_TEAM)->GetMember().begin(); i != GetTeam(COLONIST_TEAM)->GetMember().end(); ++i)
2630 if ((*i)->IsEnabled()) return false;
2631 return true;
2635 // returns true if date is christmaseve or day
2636 truth game::IsXMas () {
2637 time_t Time = time(0);
2638 struct tm *TM = localtime(&Time);
2639 return (TM->tm_mon == 11 && (TM->tm_mday == 24 || TM->tm_mday == 25));
2643 int game::AddToItemDrawVector (const itemvector &What) {
2644 ItemDrawVector.push_back(What);
2645 return ItemDrawVector.size()-1;
2649 v2 ItemDisplacement[3][3] = {
2650 { v2(0, 0), ERROR_V2, ERROR_V2 },
2651 { v2(-2, -2), v2(2, 2), ERROR_V2 },
2652 { v2(-4, -4), v2(0, 0), v2(4, 4) }
2656 void game::ItemEntryDrawer (bitmap *Bitmap, v2 Pos, uInt I) {
2657 blitdata B = {
2658 Bitmap,
2659 { 0, 0 },
2660 { 0, 0 },
2661 { TILE_SIZE, TILE_SIZE },
2662 { NORMAL_LUMINANCE },
2663 TRANSPARENT_COLOR,
2664 ALLOW_ANIMATE
2666 itemvector ItemVector = ItemDrawVector[I];
2667 int Amount = Min<int>(ItemVector.size(), 3);
2668 for (int c = 0; c < Amount; ++c) {
2669 v2 Displacement = ItemDisplacement[Amount-1][c];
2670 if (!ItemVector[0]->HasNormalPictureDirection()) Displacement.X = -Displacement.X;
2671 B.Dest = Pos+Displacement;
2672 if (ItemVector[c]->AllowAlphaEverywhere()) B.CustomData |= ALLOW_ALPHA;
2673 ItemVector[c]->Draw(B);
2674 B.CustomData &= ~ALLOW_ALPHA;
2676 if (ItemVector.size() > 3) {
2677 B.Src.X = 0;
2678 B.Src.Y = 16;
2679 B.Dest = ItemVector[0]->HasNormalPictureDirection() ? Pos+v2(11, -2) : Pos+v2(-2, -2);
2680 B.Flags = 0;
2681 igraph::GetSymbolGraphic()->NormalMaskedBlit(B);
2686 int game::AddToCharacterDrawVector (character *What) {
2687 CharacterDrawVector.push_back(What);
2688 return CharacterDrawVector.size()-1;
2692 void game::CharacterEntryDrawer (bitmap *Bitmap, v2 Pos, uInt I) {
2693 if (CharacterDrawVector[I]) {
2694 blitdata B = {
2695 Bitmap,
2696 { 0, 0 },
2697 { Pos.X, Pos.Y },
2698 { TILE_SIZE, TILE_SIZE },
2699 { NORMAL_LUMINANCE },
2700 TRANSPARENT_COLOR,
2701 ALLOW_ANIMATE|ALLOW_ALPHA
2703 CharacterDrawVector[I]->DrawBodyParts(B);
2708 void game::GodEntryDrawer (bitmap *Bitmap, v2 Pos, uInt I) {
2709 blitdata B = {
2710 Bitmap,
2711 { I << 4, 0 },
2712 { Pos.X, Pos.Y },
2713 { TILE_SIZE, TILE_SIZE },
2714 { 0 },
2715 TRANSPARENT_COLOR,
2718 igraph::GetSymbolGraphic()->NormalMaskedBlit(B);
2722 character *game::GetSumo () {
2723 return GetCurrentLevel()->GetLSquare(SUMO_ROOM_POS)->GetRoom()->GetMaster();
2727 truth game::TryToEnterSumoArena () {
2728 character *Sumo = GetSumo();
2729 if (!Sumo || !Sumo->IsEnabled() || Sumo->GetRelation(Player) == HOSTILE || !Player->CanBeSeenBy(Sumo)) return true;
2730 if (TweraifIsFree()) {
2731 ADD_MESSAGE("\"You started this stupid revolution, after which I've been constantly hungry. Get lost!\"");
2732 return false;
2734 if (PlayerIsSumoChampion()) {
2735 ADD_MESSAGE("\"I don't really enjoy losing, especially many times to the same guy. Go away.\"");
2736 return false;
2738 if (Player->IsPolymorphed()) {
2739 ADD_MESSAGE("\"Don't try to cheat. Come back when you're normal again.\"");
2740 return false;
2742 if (Player->GetHungerState() < SATIATED) {
2743 ADD_MESSAGE("\"Your figure is too slender for this sport. Eat a lot more and come back.\"");
2744 return false;
2746 if (Player->GetHungerState() < BLOATED) {
2747 ADD_MESSAGE("\"You're still somewhat too thin. Eat some more and we'll compete.\"");
2748 return false;
2750 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.\"");
2751 if (!TruthQuestion("Do you want to challenge him? [y/N]")) return false;
2752 pool::AbortBe();
2753 SumoWrestling = true;
2754 character *MirrorPlayer = Player->Duplicate(IGNORE_PROHIBITIONS);
2755 character *MirrorSumo = Sumo->Duplicate(IGNORE_PROHIBITIONS);
2756 SetPlayer(MirrorPlayer);
2757 charactervector Spectators;
2758 if (Player->GetTeam()->GetRelation(GetTeam(TOURIST_GUIDE_TEAM)) != HOSTILE &&
2759 Player->GetTeam()->GetRelation(GetTeam(TOURIST_TEAM)) != HOSTILE) {
2760 GetTeam(TOURIST_GUIDE_TEAM)->MoveMembersTo(Spectators);
2761 GetTeam(TOURIST_TEAM)->MoveMembersTo(Spectators);
2763 GetCurrentDungeon()->SaveLevel(SaveName(), 0);
2764 charactervector test;
2765 EnterArea(test, 1, STAIRS_UP);
2766 MirrorSumo->PutTo(SUMO_ARENA_POS+v2(6, 5));
2767 MirrorSumo->ChangeTeam(GetTeam(SUMO_TEAM));
2768 GetCurrentLevel()->GetLSquare(SUMO_ARENA_POS)->GetRoom()->SetMasterID(MirrorSumo->GetID());
2769 for (uInt c = 0; c < Spectators.size(); ++c) Spectators[c]->PutToOrNear(SUMO_ARENA_POS + v2(6, 10));
2770 throw areachangerequest();
2771 return true;
2775 truth game::TryToExitSumoArena () {
2776 if (GetTeam(PLAYER_TEAM)->GetRelation(GetTeam(NEW_ATTNAM_TEAM)) == HOSTILE) return true;
2777 itemvector IVector;
2778 charactervector CVector;
2779 if (IsSumoWrestling()) {
2780 if (TruthQuestion("Do you really wish to give up?")) return EndSumoWrestling(LOST);
2781 return false;
2782 } else {
2783 pool::AbortBe();
2784 Player->Remove();
2785 GetCurrentLevel()->CollectEverything(IVector, CVector);
2786 GetCurrentDungeon()->SaveLevel(SaveName(), 1);
2787 std::vector<character*> test;
2788 EnterArea(test, 0, STAIRS_DOWN);
2789 Player->GetStackUnder()->AddItems(IVector);
2790 if (!IVector.empty()) {
2791 character *Sumo = GetSumo();
2792 if (Sumo && Sumo->GetRelation(Player) != HOSTILE && Player->CanBeSeenBy(Sumo)) ADD_MESSAGE("\"Don't leave anything there, please.\"");
2794 v2 PlayerPos = Player->GetPos();
2795 for (uInt c = 0; c < CVector.size(); ++c) CVector[c]->PutNear(PlayerPos);
2796 throw areachangerequest();
2797 return true;
2802 truth game::EndSumoWrestling (int Result) {
2803 pool::AbortBe();
2804 msgsystem::LeaveBigMessageMode();
2805 if (Result == LOST) AskForKeyPress("You lose. [press any key to continue]");
2806 else if (Result == WON) AskForKeyPress("You win! [press any key to continue]");
2807 else if (Result == DISQUALIFIED) AskForKeyPress("You are disqualified! [press any key to continue]");
2808 character *Sumo = GetCurrentLevel()->GetLSquare(SUMO_ARENA_POS)->GetRoom()->GetMaster();
2809 /* We'll make a throw soon so deletes are allowed */
2810 if (Sumo) {
2811 Sumo->Remove();
2812 delete Sumo;
2814 Player->Remove();
2815 delete Player;
2816 SetPlayer(0);
2817 itemvector IVector;
2818 charactervector CVector;
2819 GetCurrentLevel()->CollectEverything(IVector, CVector);
2820 GetCurrentDungeon()->SaveLevel(SaveName(), 1);
2821 charactervector test;
2822 EnterArea(test, 0, STAIRS_DOWN);
2823 SumoWrestling = false;
2824 Player->GetStackUnder()->AddItems(IVector);
2825 v2 PlayerPos = Player->GetPos();
2826 for (uInt c = 0; c < CVector.size(); ++c) CVector[c]->PutNear(PlayerPos);
2827 if (Result == LOST) ADD_MESSAGE("\"I hope you've learned your lesson now!\"");
2828 else if (Result == DISQUALIFIED) ADD_MESSAGE("\"Don't do that again or I'll be really angry!\"");
2829 else {
2830 PlayerSumoChampion = true;
2831 character *Sumo = GetSumo();
2832 festring Msg = Sumo->GetName(DEFINITE)+" seems humbler than before. \"Darn. You bested me.\n";
2833 Msg << "Here's a little something as a reward\", " << Sumo->GetPersonalPronoun() << " says and hands you a belt of levitation.\n\"";
2834 (belt::Spawn(BELT_OF_LEVITATION))->MoveTo(Player->GetStack());
2835 Msg << "Allow me to also teach you a few nasty martial art tricks the years have taught me.\"";
2836 Player->GetCWeaponSkill(UNARMED)->AddHit(100000);
2837 Player->GetCWeaponSkill(KICK)->AddHit(100000);
2838 character *Imperialist = GetCurrentLevel()->GetLSquare(5, 5)->GetRoom()->GetMaster();
2839 if (Imperialist && Imperialist->GetRelation(Player) != HOSTILE) {
2840 v2 Pos = Player->GetPos()+v2(0, 1);
2841 GetCurrentLevel()->GetLSquare(Pos)->KickAnyoneStandingHereAway();
2842 Imperialist->Remove();
2843 Imperialist->PutTo(Pos);
2844 Msg << "\n\nSuddenly you notice " << Imperialist->GetName(DEFINITE) << " has also entered.\n"
2845 "\"I see we have a promising fighter among us. I had already heard of your\n"
2846 "adventures outside the village, but hardly could I believe that one day you\n"
2847 "would defeat even the mighty Huang Ming Pong! A hero such as you is bound\n"
2848 "to become world famous, and can earn a fortune if wealthy sponsors are behind\n"
2849 "him. May I therefore propose a mutually profitable contract: I'll give you this\n"
2850 "nice shirt with my company's ad, and you'll wear it as you journey bravely to\n"
2851 "the unknown and fight epic battles against the limitless minions of evil. I'll\n"
2852 "reward you well when you return, depending on how much you have used it.\"";
2853 Player->GetStack()->AddItem(decosadshirt::Spawn());
2855 TextScreen(Msg);
2856 GetCurrentArea()->SendNewDrawRequest();
2857 DrawEverything();
2859 Player->EditNP(-25000);
2860 Player->CheckStarvationDeath(CONST_S("exhausted after controlling a mirror image for too long"));
2861 throw areachangerequest();
2862 return true;
2866 rain *game::ConstructGlobalRain () {
2867 return new rain(GlobalRainLiquid, static_cast<lsquare*>(GetSquareInLoad()), GlobalRainSpeed, MONSTER_TEAM, false);
2871 v2 game::GetSunLightDirectionVector () {
2872 int Index = Tick % 48000 / 1000;
2873 /* Should have the same sign as sin(PI * Index / 24) and XTable[Index] /
2874 YTable[Index] should equal roughly -tan(PI * Index / 24). Also, vector
2875 (XTable[Index], YTable[Index]) + P should not be a valid position of
2876 any possible level L for any P belonging to L. */
2877 static int XTable[48] = {
2878 0, 1000, 1000, 1000, 1000, 1000,
2879 1000, 1303, 1732, 2414, 3732, 7596,
2880 1000, 7596, 3732, 2414, 1732, 1303,
2881 1000, 1000, 1000, 1000, 1000, 1000,
2882 0, -1000, -1000, -1000, -1000, -1000,
2883 -1000, -1303, -1732, -2414, -3732, -7596,
2884 -1000, -7596, -3732, -2414, -1732, -1303,
2885 -1000, -1000, -1000, -1000, -1000, -1000 };
2886 /* Should have the same sign as -cos(PI * Index / 24) */
2887 static int YTable[48] = { -1000, -7596, -3732, -2414, -1732, -1303,
2888 -1000, -1000, -1000, -1000, -1000, -1000,
2889 0, 1000, 1000, 1000, 1000, 1000,
2890 1000, 1303, 1732, 2414, 3732, 7596,
2891 1000, 7596, 3732, 2414, 1732, 1303,
2892 1000, 1000, 1000, 1000, 1000, 1000,
2893 0, -1000, -1000, -1000, -1000, -1000,
2894 -1000, -1303, -1732, -2414, -3732, -7596 };
2895 return v2(XTable[Index], YTable[Index]);
2899 int game::CalculateMinimumEmitationRadius (col24 E) {
2900 int MaxElement = Max(GetRed24(E), GetGreen24(E), GetBlue24(E));
2901 return int(sqrt(double(MaxElement << 7) / LIGHT_BORDER - 120.));
2905 feuLong game::IncreaseSquarePartEmitationTicks () {
2906 if ((SquarePartEmitationTick += 2) == 0x100) {
2907 CurrentLevel->InitSquarePartEmitationTicks();
2908 SquarePartEmitationTick = 2;
2910 return SquarePartEmitationTick;
2914 bool game::Wish (character *Wisher, cchar *MsgSingle, cchar *MsgPair, bool canAbort) {
2915 for (;;) {
2916 festring oldDef = DefaultWish;
2917 festring Temp = DefaultQuestion(CONST_S("What do you want to wish for?"), DefaultWish);
2918 if (DefaultWish == "nothing" && canAbort) {
2919 DefaultWish = oldDef;
2920 return false;
2922 if (Temp == "socm") Temp = "scroll of change material";
2923 else if (Temp == "soc") Temp = "scroll of charging";
2924 else if (Temp == "sodm") Temp = "scroll of detect material";
2925 else if (Temp == "soea") Temp = "scroll of enchant armor";
2926 else if (Temp == "soew") Temp = "scroll of enchant weapon";
2927 else if (Temp == "sof") Temp = "scroll of fireballs";
2928 else if (Temp == "sogc") Temp = "scroll of golem creation";
2929 else if (Temp == "sohm") Temp = "scroll of harden material";
2930 else if (Temp == "sor") Temp = "scroll of repair";
2931 else if (Temp == "sot") Temp = "scroll of taming";
2932 else if (Temp == "sotp") Temp = "scroll of teleportation";
2933 else if (Temp == "sow") Temp = "scroll of wishing";
2934 else if (Temp == "vodka") Temp = "bottle full of vodka";
2935 else if (Temp == "troll blood") Temp = "bottle full of troll blood";
2936 item *TempItem = protosystem::CreateItem(Temp, Wisher->IsPlayer());
2937 if (TempItem) {
2938 Wisher->GetStack()->AddItem(TempItem);
2939 TempItem->SpecialGenerationHandler();
2940 if (TempItem->HandleInPairs()) ADD_MESSAGE(MsgPair, TempItem->CHAR_NAME(PLURAL));
2941 else ADD_MESSAGE(MsgSingle, TempItem->CHAR_NAME(INDEFINITE));
2942 return true;
2948 festring game::DefaultQuestion (festring Topic, festring &Default, stringkeyhandler KeyHandler) {
2949 festring ShortDefault = Default;
2950 if (Default.GetSize() > 29) {
2951 ShortDefault.Resize(27);
2952 ShortDefault = ShortDefault << CONST_S("...");
2954 if (!Default.IsEmpty()) Topic << " [" << ShortDefault << ']';
2955 festring Answer = StringQuestion(Topic, WHITE, 0, 80, false, KeyHandler);
2956 if (Answer.IsEmpty()) Answer = Default;
2957 return Default = Answer;
2961 void game::GetTime (ivantime &Time) {
2962 Time.Hour = 12 + Tick / 2000;
2963 Time.Day = Time.Hour / 24 + 1;
2964 Time.Hour %= 24;
2965 Time.Min = Tick % 2000 * 60 / 2000;
2969 truth NameOrderer (character *C1, character *C2) {
2970 return festring::IgnoreCaseCompare(C1->GetName(UNARTICLED), C2->GetName(UNARTICLED));
2974 truth game::PolymorphControlKeyHandler (int Key, festring &String) {
2975 if (Key == '?') {
2976 felist List(CONST_S("List of known creatures and their intelligence requirements"));
2977 SetStandardListAttributes(List);
2978 List.SetPageLength(15);
2979 List.AddFlags(SELECTABLE);
2980 protosystem::CreateEverySeenCharacter(CharacterDrawVector);
2981 std::sort(CharacterDrawVector.begin(), CharacterDrawVector.end(), NameOrderer);
2982 List.SetEntryDrawer(CharacterEntryDrawer);
2983 std::vector<festring> StringVector;
2984 uInt c;
2985 for (c = 0; c < CharacterDrawVector.size(); ++c) {
2986 character *Char = CharacterDrawVector[c];
2987 if (Char->CanBeWished()) {
2988 festring Entry;
2989 Char->AddName(Entry, UNARTICLED);
2990 StringVector.push_back(Entry);
2991 int Req = Char->GetPolymorphIntelligenceRequirement();
2992 if (Char->IsSameAs(Player) || (Player->GetPolymorphBackup() && Player->GetPolymorphBackup()->IsSameAs(Char))) Req = 0;
2993 Entry << " (" << Req << ')';
2994 int Int = Player->GetAttribute(INTELLIGENCE);
2995 List.AddEntry(Entry, Req > Int ? RED : LIGHT_GRAY, 0, c);
2998 int Chosen = List.Draw();
2999 for (c = 0; c < CharacterDrawVector.size(); ++c) delete CharacterDrawVector[c];
3000 if (!(Chosen & FELIST_ERROR_BIT)) String = StringVector[Chosen];
3001 CharacterDrawVector.clear();
3002 return true;
3004 return false;
3008 outputfile &operator << (outputfile &SaveFile, const killdata &Value) {
3009 SaveFile << Value.Amount << Value.DangerSum << Value.Reason;
3010 return SaveFile;
3014 inputfile &operator >> (inputfile &SaveFile, killdata &Value) {
3015 SaveFile >> Value.Amount >> Value.DangerSum >> Value.Reason;
3016 return SaveFile;
3020 outputfile &operator << (outputfile &SaveFile, const killreason &Value) {
3021 SaveFile << Value.Amount << Value.String;
3022 return SaveFile;
3026 inputfile &operator >> (inputfile &SaveFile, killreason &Value) {
3027 SaveFile >> Value.Amount >> Value.String;
3028 return SaveFile;
3032 truth DistanceOrderer (character *C1, character *C2) {
3033 v2 PlayerPos = PLAYER->GetPos();
3034 v2 Pos1 = C1->GetPos();
3035 v2 Pos2 = C2->GetPos();
3036 int D1 = Max(abs(Pos1.X - PlayerPos.X), abs(Pos1.Y - PlayerPos.Y));
3037 int D2 = Max(abs(Pos2.X - PlayerPos.X), abs(Pos2.Y - PlayerPos.Y));
3038 if (D1 != D2) return D1 < D2;
3039 if (Pos1.Y != Pos2.Y) return Pos1.Y < Pos2.Y;
3040 return Pos1.X < Pos2.X;
3044 truth game::FillPetVector (cchar *Verb) {
3045 PetVector.clear();
3046 team *Team = GetTeam(PLAYER_TEAM);
3047 for (std::list<character*>::const_iterator i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i)
3048 if ((*i)->IsEnabled() && !(*i)->IsPlayer() && (*i)->CanBeSeenByPlayer()) PetVector.push_back(*i);
3049 if (PetVector.empty()) {
3050 ADD_MESSAGE("You don't detect any friends to %s.", Verb);
3051 return false;
3053 std::sort(PetVector.begin(), PetVector.end(), DistanceOrderer);
3054 LastPetUnderCursor = PetVector[0];
3055 return true;
3059 truth game::CommandQuestion () {
3060 if (!FillPetVector("command")) return false;
3061 character *Char;
3062 if (PetVector.size() == 1) Char = PetVector[0];
3063 else {
3064 v2 Pos = PetVector[0]->GetPos();
3065 Pos = PositionQuestion(CONST_S("Whom do you wish to command? [direction keys/'+'/'-'/'a'll/space/esc]"), Pos, &PetHandler, &CommandKeyHandler);
3066 if (Pos == ERROR_V2) return false;
3067 if (Pos == ABORT_V2) return true;
3068 Char = CurrentArea->GetSquare(Pos)->GetCharacter();
3069 if (!Char || !Char->CanBeSeenByPlayer()) {
3070 ADD_MESSAGE("You don't see anyone here to command.");
3071 return false;
3073 if (Char->IsPlayer()) {
3074 ADD_MESSAGE("You do that all the time.");
3075 return false;
3077 if (!Char->IsPet()) {
3078 ADD_MESSAGE("%s refuses to be commanded by you.", Char->CHAR_NAME(DEFINITE));
3079 return false;
3082 return Char->IssuePetCommands();
3086 void game::NameQuestion () {
3087 if (!FillPetVector("name")) return;
3088 if (PetVector.size() == 1) PetVector[0]->TryToName();
3089 else PositionQuestion(CONST_S("Who do you want to name? [direction keys/'+'/'-'/'n'ame/esc]"), PetVector[0]->GetPos(), &PetHandler, &NameKeyHandler);
3093 void game::PetHandler (v2 CursorPos) {
3094 character *Char = CurrentArea->GetSquare(CursorPos)->GetCharacter();
3095 if (Char && Char->CanBeSeenByPlayer() && Char->IsPet() && !Char->IsPlayer()) CursorData = RED_CURSOR|CURSOR_TARGET|CURSOR_SHADE;
3096 else CursorData = RED_CURSOR;
3097 if (Char && !Char->IsPlayer() && Char->IsPet()) LastPetUnderCursor = Char;
3101 v2 game::CommandKeyHandler (v2 CursorPos, int Key) {
3102 if (SelectPet(Key)) return LastPetUnderCursor->GetPos();
3103 if (Key == 'a' || Key == 'A') return (CommandAll() ? ABORT_V2 : ERROR_V2);
3104 return CursorPos;
3108 truth game::SelectPet (int Key) {
3109 if (Key == '+') {
3110 for (uInt c = 0; c < PetVector.size(); ++c) {
3111 if (PetVector[c] == LastPetUnderCursor) {
3112 if (++c == PetVector.size()) c = 0;
3113 LastPetUnderCursor = PetVector[c];
3114 return true;
3117 } else if (Key == '-') {
3118 for (uInt c = 0; c < PetVector.size(); ++c) {
3119 if (PetVector[c] == LastPetUnderCursor) {
3120 if (!c) c = PetVector.size();
3121 LastPetUnderCursor = PetVector[--c];
3122 return true;
3126 return false;
3130 void game::CommandScreen (cfestring &Topic, feuLong PossibleFlags, feuLong ConstantFlags, feuLong &VaryFlags, feuLong &Flags) {
3131 static cchar *CommandDescription[COMMAND_FLAGS] = {
3132 "Follow me",
3133 "Flee from enemies",
3134 "Don't change your equipment",
3135 "Don't consume anything valuable"
3137 felist List(Topic);
3138 SetStandardListAttributes(List);
3139 List.AddFlags(SELECTABLE);
3140 List.AddDescription(CONST_S(""));
3141 List.AddDescription(CONST_S("Command Active?"));
3142 for (;;) {
3143 int c, i;
3144 for (c = 0; c < COMMAND_FLAGS; ++c) {
3145 if (1 << c & PossibleFlags) {
3146 truth Changeable = !(1 << c & ConstantFlags);
3147 festring Entry;
3148 if (Changeable) {
3149 Entry = CommandDescription[c];
3150 Entry.Resize(60);
3151 } else {
3152 Entry << " " << CommandDescription[c];
3153 Entry.Resize(63);
3155 if (1 << c & VaryFlags) Entry << "varies"; else Entry << (1 << c & Flags ? "yes" : "no");
3156 List.AddEntry(Entry, Changeable ? LIGHT_GRAY : DARK_GRAY, 0, NO_IMAGE, Changeable);
3159 int Chosen = List.Draw();
3160 if (Chosen & FELIST_ERROR_BIT) return;
3161 for (c = 0, i = 0; c < COMMAND_FLAGS; ++c) {
3162 if (1 << c & PossibleFlags && !(1 << c & ConstantFlags) && i++ == Chosen) {
3163 if (1 << c & VaryFlags) {
3164 VaryFlags &= ~(1 << c);
3165 Flags |= 1 << c;
3166 } else Flags ^= 1 << c;
3167 break;
3170 List.Empty();
3171 DrawEverythingNoBlit();
3176 truth game::CommandAll () {
3177 feuLong PossibleFlags = 0, ConstantFlags = ALL_COMMAND_FLAGS, VaryFlags = 0, OldFlags = 0;
3178 uInt c1, c2;
3179 for (c1 = 0; c1 < PetVector.size(); ++c1) {
3180 ConstantFlags &= PetVector[c1]->GetConstantCommandFlags();
3181 feuLong C = PetVector[c1]->GetCommandFlags();
3182 feuLong ThisPossible = PetVector[c1]->GetPossibleCommandFlags();
3183 for (c2 = 0; c2 < COMMAND_FLAGS; ++c2)
3184 if (1 << c2 & PossibleFlags & ThisPossible && (1 << c2 & C) != (1 << c2 & OldFlags)) VaryFlags |= 1 << c2;
3185 PossibleFlags |= ThisPossible;
3186 OldFlags |= C & ThisPossible;
3188 if (!PossibleFlags) {
3189 ADD_MESSAGE("Not a single creature in your visible team can be commanded.");
3190 return false;
3192 feuLong NewFlags = OldFlags;
3193 CommandScreen(CONST_S("Issue commands to whole visible team"), PossibleFlags, ConstantFlags, VaryFlags, NewFlags);
3194 truth Change = false;
3195 for (c1 = 0; c1 < PetVector.size(); ++c1) {
3196 character *Char = PetVector[c1];
3197 if (!Char->IsConscious()) continue;
3198 feuLong OldC = Char->GetCommandFlags();
3199 feuLong ConstC = Char->GetConstantCommandFlags();
3200 feuLong ThisC = (NewFlags & Char->GetPossibleCommandFlags() & ~(ConstC|VaryFlags)) | (OldC & (ConstC|VaryFlags));
3201 if (ThisC != OldC) Change = true;
3202 Char->SetCommandFlags(ThisC);
3204 if (!Change) return false;
3205 Player->EditAP(-500);
3206 Player->EditExperience(CHARISMA, 50, 1 << 7);
3207 return true;
3211 col16 game::GetAttributeColor (int I) {
3212 int Delta = GetTick()-LastAttributeChangeTick[I];
3213 if (OldAttribute[I] == NewAttribute[I] || Delta >= 510) return WHITE;
3214 if (OldAttribute[I] < NewAttribute[I]) return MakeRGB16(255, 255, Delta >> 1);
3215 return MakeRGB16(255, Delta >> 1, Delta >> 1);
3219 void game::UpdateAttributeMemory () {
3220 for (int c = 0; c < ATTRIBUTES; ++c) {
3221 int A = Player->GetAttribute(c);
3222 if (A != NewAttribute[c]) {
3223 OldAttribute[c] = NewAttribute[c];
3224 NewAttribute[c] = A;
3225 LastAttributeChangeTick[c] = GetTick();
3231 void game::InitAttributeMemory () {
3232 for (int c = 0; c < ATTRIBUTES; ++c) OldAttribute[c] = NewAttribute[c] = Player->GetAttribute(c);
3236 void game::TeleportHandler (v2 CursorPos) {
3237 if ((CursorPos-Player->GetPos()).GetLengthSquare() > Player->GetTeleportRangeSquare())
3238 CursorData = BLUE_CURSOR|CURSOR_TARGET|CURSOR_SHADE;
3239 else
3240 CursorData = RED_CURSOR|CURSOR_TARGET|CURSOR_SHADE;
3244 double game::GetGameSituationDanger () {
3245 double SituationDanger = 0;
3246 character *Player = GetPlayer();
3247 truth PlayerStuck = Player->IsStuck();
3248 v2 PlayerPos = Player->GetPos();
3249 character *TruePlayer = Player;
3250 if (PlayerStuck) (Player = Player->Duplicate(IGNORE_PROHIBITIONS))->ChangeTeam(0);
3251 for (int c1 = 0; c1 < GetTeams(); ++c1)
3252 if (GetTeam(c1)->GetRelation(GetTeam(PLAYER_TEAM)) == HOSTILE)
3253 for (std::list<character*>::const_iterator i1 = GetTeam(c1)->GetMember().begin(); i1 != GetTeam(c1)->GetMember().end(); ++i1) {
3254 character *Enemy = *i1;
3255 if (Enemy->IsEnabled() && Enemy->CanAttack() && (Enemy->CanMove() || Enemy->GetPos().IsAdjacent(PlayerPos))) {
3256 truth EnemyStuck = Enemy->IsStuck();
3257 v2 EnemyPos = Enemy->GetPos();
3258 truth Sees = TruePlayer->CanBeSeenBy(Enemy);
3259 character *TrueEnemy = Enemy;
3260 if (EnemyStuck) Enemy = Enemy->Duplicate(IGNORE_PROHIBITIONS);
3261 double PlayerTeamDanger = 1/Enemy->GetSituationDanger(Player, EnemyPos, PlayerPos, Sees);
3262 for (int c2 = 0; c2 < GetTeams(); ++c2)
3263 if (GetTeam(c2)->GetRelation(GetTeam(c1)) == HOSTILE)
3264 for (std::list<character*>::const_iterator i2 = GetTeam(c2)->GetMember().begin(); i2 != GetTeam(c2)->GetMember().end(); ++i2) {
3265 character *Friend = *i2;
3266 if (Friend->IsEnabled() && !Friend->IsPlayer() && Friend->CanAttack() && (Friend->CanMove() || Friend->GetPos().IsAdjacent(EnemyPos))) {
3267 v2 FriendPos = Friend->GetPos();
3268 truth Sees = TrueEnemy->CanBeSeenBy(Friend);
3269 if (Friend->IsStuck()) {
3270 Friend = Friend->Duplicate(IGNORE_PROHIBITIONS);
3271 PlayerTeamDanger += Friend->GetSituationDanger(Enemy, FriendPos, EnemyPos, Sees) * .2;
3272 delete Friend;
3273 } else PlayerTeamDanger += Friend->GetSituationDanger(Enemy, FriendPos, EnemyPos, Sees);
3276 if (EnemyStuck) {
3277 PlayerTeamDanger *= 5;
3278 delete Enemy;
3280 SituationDanger += 1 / PlayerTeamDanger;
3283 Player->ModifySituationDanger(SituationDanger);
3284 if (PlayerStuck) {
3285 SituationDanger *= 2;
3286 delete Player;
3288 return SituationDanger;
3292 sLong game::GetTimeSpent () {
3293 return time::TimeAdd(time::TimeDifference(time(0),LastLoad), TimePlayedBeforeLastLoad);
3297 outputfile &operator << (outputfile &SaveFile, const massacreid &MI) {
3298 SaveFile << MI.Type << MI.Config << MI.Name;
3299 return SaveFile;
3303 inputfile &operator >> (inputfile &SaveFile, massacreid &MI) {
3304 SaveFile >> MI.Type >> MI.Config >> MI.Name;
3305 return SaveFile;
3309 truth game::PlayerIsRunning () {
3310 return PlayerRunning && Player->CanMove();
3314 void game::AddSpecialCursor (v2 Pos, int Data) {
3315 SpecialCursorPos.push_back(Pos);
3316 SpecialCursorData.push_back(Data);
3320 void game::RemoveSpecialCursors () {
3321 SpecialCursorPos.clear();
3322 SpecialCursorData.clear();
3326 void game::LearnAbout (god *Who) {
3327 Who->SetIsKnown(true);
3328 /* slightly slow, but doesn't matter since this is run so rarely */
3329 if (PlayerKnowsAllGods() && !game::PlayerHasReceivedAllGodsKnownBonus) {
3330 GetPlayer()->ApplyAllGodsKnownBonus();
3331 game::PlayerHasReceivedAllGodsKnownBonus = true;
3336 truth game::PlayerKnowsAllGods () {
3337 for (int c = 1; c <= GODS; ++c) if (!GetGod(c)->IsKnown()) return false;
3338 return true;
3342 void game::AdjustRelationsToAllGods (int Amount) {
3343 for (int c = 1; c <= GODS; ++c) GetGod(c)->AdjustRelation(Amount);
3347 void game::SetRelationsToAllGods (int Amount) {
3348 for (int c = 1; c <= GODS; ++c) GetGod(c)->SetRelation(Amount);
3352 void game::ShowDeathSmiley (bitmap *Buffer, truth) {
3353 static blitdata B = {
3355 { 0, 0 },
3356 { (RES.X >> 1) - 24, RES.Y * 4 / 7 - 24 },
3357 { 48, 48 },
3358 { 0 },
3359 TRANSPARENT_COLOR,
3362 int Tick = globalwindowhandler::UpdateTick();
3363 if (((Tick >> 1) & 31) == 1) B.Src.X = 48;
3364 else if (((Tick >> 1) & 31) == 2) B.Src.X = 96;
3365 else B.Src.X = 0;
3366 B.Bitmap = Buffer;
3367 igraph::GetSmileyGraphic()->NormalBlit(B);
3368 if (Buffer == DOUBLE_BUFFER) graphics::BlitDBToScreen();
3372 static int doListSelector (felist &list, int defsel, int cnt) {
3373 game::SetStandardListAttributes(list);
3374 list.AddFlags(SELECTABLE | FELIST_NO_BADKEY_EXIT);
3375 if (defsel > 0) list.SetSelected(defsel);
3376 uInt sel = list.Draw();
3377 list.Empty();
3378 list.RemoveFlags(SELECTABLE | FELIST_NO_BADKEY_EXIT);
3379 if (sel & FELIST_ERROR_BIT) return -1;
3380 if (sel >= (uInt)cnt) return -1;
3381 return (int)sel;
3385 int game::ListSelector (int defsel, const cfestring title, ...) {
3386 int cnt = 0;
3387 va_list items;
3388 va_start(items, title);
3390 felist list(title);
3391 for (;;) {
3392 const char *s = va_arg(items, const char *);
3393 if (!s) break;
3394 list.AddEntry(s, LIGHT_GRAY);
3395 cnt++;
3397 va_end(items);
3398 return doListSelector(list, defsel, cnt);
3402 int game::ListSelectorArray (int defsel, cfestring &title, const char *items[]) {
3403 int cnt = 0;
3404 felist list(title);
3405 for (;;) {
3406 if (!items[cnt]) break;
3407 list.AddEntry(items[cnt], LIGHT_GRAY);
3408 cnt++;
3410 return doListSelector(list, defsel, cnt);
3414 void game::ClearEventData () {
3415 mChar = 0;
3416 mActor = 0;
3417 mSecondActor = 0;
3418 mItem = 0;
3422 // '.': string or number
3423 // 'n': number
3424 // 's': string
3425 // '*': collect all args
3426 int game::ParseFuncArgs (cfestring &types, std::vector<FuncArg> &args, TextInput *fl, truth noterm) {
3427 festring s;
3428 sLong n;
3429 truth isStr;
3430 if (!fl) fl = mFEStack.top();
3431 args.clear();
3432 for (unsigned int f = 0; f < types.GetSize(); f++) {
3433 switch (types[f]) {
3434 case '.':
3435 s = fl->ReadStringOrNumber(&n, &isStr, true);
3436 if (isStr) args.push_back(FuncArg(s)); else args.push_back(FuncArg(n));
3437 break;
3438 case 'n':
3439 n = fl->ReadNumber(0xFF, true);
3440 args.push_back(FuncArg(n));
3441 break;
3442 case '*':
3443 for (;;) {
3444 s = fl->ReadStringOrNumber(&n, &isStr, true);
3445 if (isStr) args.push_back(FuncArg(s)); else args.push_back(FuncArg(n));
3446 fl->ReadWord(s, true);
3447 if (s == ";") return args.size();
3448 if (s != ",") ABORT("',' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3450 // never reached
3451 case 's':
3452 default:
3453 s = fl->ReadWord(true);
3454 args.push_back(FuncArg(s));
3455 break;
3457 if (f == types.GetSize()-1) {
3458 if (noterm) break;
3459 fl->ReadWord(s, true);
3460 if (s != ";") ABORT("';' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3461 break;
3462 } else {
3463 fl->ReadWord(s, true);
3464 if (s != ",") ABORT("',' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3467 return args.size();
3471 truth game::GetWord (festring &w) {
3472 for (;;) {
3473 TextInput *fl = mFEStack.top();
3474 fl->ReadWord(w, false);
3475 if (w == "" && fl->Eof()) {
3476 delete fl;
3477 mFEStack.pop();
3478 if (mFEStack.empty()) return false;
3479 continue;
3481 if (w == "Include") {
3482 fl->ReadWord(w, true);
3483 if (fl->ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3484 TextInput *fl = new TextInputFile(inputfile::buildIncludeName(fl->GetFileName(), w), &game::GetGlobalValueMap(), true);
3485 fl->setGetVarCB(game::ldrGetVar);
3486 mFEStack.push(fl);
3487 continue;
3489 if (w == "Message") {
3490 fl->ReadWord(w, true);
3491 if (fl->ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3492 fprintf(stderr, "MESSAGE: %s\n", w.CStr());
3493 continue;
3495 return true;
3500 void game::SkipBlock (truth brcEaten) {
3501 festring w;
3502 if (!brcEaten) {
3503 mFEStack.top()->ReadWord(w, true);
3504 if (w != "{") ABORT("'{' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3506 int cnt = 1;
3507 for (;;) {
3508 mFEStack.top()->ReadWord(w, true);
3509 if (w == "{") cnt++;
3510 else if (w == "}") {
3511 if (--cnt < 1) break;
3517 truth game::DoOnEvent (truth brcEaten, truth AllowScript) {
3518 // do; only funcalls for now
3519 truth eaten = AllowScript ? true : false;
3520 festring w;
3521 if (!brcEaten) {
3522 mFEStack.top()->ReadWord(w, true);
3523 if (w != "{") ABORT("'{' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3525 for (;;) {
3526 if (!GetWord(w)) {
3527 if (AllowScript) break;
3528 ABORT("Unexpected end of file %s!", mFEStack.top()->GetFileName().CStr());
3530 //fprintf(stderr, " :[%s]\n", w.CStr());
3531 if (w == "}") {
3532 if (AllowScript) ABORT("Unexpected '}' in AllowScript file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3533 break;
3535 if (w == ";") continue;
3536 if (w == "@") {
3537 mFEStack.top()->ReadWord(w, true);
3538 if (mFEStack.top()->ReadWord(true) != "=") ABORT("'=' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3539 //fprintf(stderr, "setvar: %s\n", w.CStr());
3540 if (w == "money") {
3541 sLong n = mFEStack.top()->ReadNumber(true);
3542 if (n < 0) n = 0;
3543 if (mChar) mChar->SetMoney(n);
3544 continue;
3546 if (w == "result") {
3547 mResult = mFEStack.top()->ReadNumber(true);
3548 continue;
3550 ABORT("Unknown var [%s] in file %s at line %d!", w.CStr(), mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3551 } else {
3552 //mFEStack.top()->ReadWord(w, true);
3553 std::vector<FuncArg> args;
3554 //fprintf(stderr, "funcall: %s\n", w.CStr());
3556 if (w == "AddItem") {
3557 ParseFuncArgs("s", args);
3558 item *it = protosystem::CreateItem(args[0].sval, false); // no output
3559 if (it) {
3560 mChar->GetStack()->AddItem(it);
3561 it->SpecialGenerationHandler();
3562 } else {
3563 ADD_MESSAGE("ERROR: no item with id \"%s\"", args[0].sval.CStr());
3565 continue;
3568 if (w == "SetMoney") {
3569 ParseFuncArgs("n", args);
3570 sLong n = args[0].ival;
3571 if (n < 0) n = 0;
3572 if (mChar) mChar->SetMoney(n);
3573 continue;
3575 if (w == "EditMoney") {
3576 ParseFuncArgs("n", args);
3577 sLong n = args[0].ival;
3578 if (mChar) mChar->EditMoney(n);
3579 continue;
3581 if (w == "AddMessage") {
3582 ParseFuncArgs("*", args);
3583 festring s;
3584 for (uInt f = 0; f < args.size(); f++) {
3585 const FuncArg &a = args[f];
3586 if (a.type == FARG_STRING) s << a.sval; else s << a.ival;
3588 ADD_MESSAGE("%s", s.CStr());
3589 continue;
3591 if (w == "EatThisEvent") {
3592 if (AllowScript) ABORT("'EatThisEvent' forbidden in AllowScripts in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3593 eaten = true;
3594 continue;
3596 if (w == "Disallow") {
3597 if (!AllowScript) ABORT("'Disallow' forbidden in not-AllowScripts in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3598 eaten = false;
3599 continue;
3601 ABORT("Unknown function [%s] in file %s at line %d!", w.CStr(), mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3602 //if (mFEStack.top()->ReadWord() != ";") ABORT("';' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3604 //ABORT("Invalid term in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3606 //fprintf(stderr, "------------\n");
3607 return eaten;
3611 //TODO: cache event scripts
3612 truth game::RunOnEvent (cfestring &ename) {
3613 static std::vector<festring> scriptFiles;
3614 static truth cached = false;
3615 truth res = false;
3617 character *old = mChar;
3618 mChar = PLAYER;
3619 if (!cached) {
3620 cached = true;
3621 // add module files
3622 auto modlist = game::GetModuleList();
3623 for (unsigned idx = modlist.size(); idx > 0; --idx) {
3624 festring infname = game::GetGameDir()+"script/"+modlist[idx-1]+"/onevent.dat";
3625 if (inputfile::fileExists(infname)) scriptFiles.push_back(infname);
3629 for (auto &cfname : scriptFiles) {
3630 TextInput *ifl = new TextInputFile(cfname, &game::GetGlobalValueMap());
3631 ifl->setGetVarCB(game::ldrGetVar);
3632 mFEStack.push(ifl);
3635 festring w;
3636 while (GetWord(w)) {
3637 if (w != "on") ABORT("'on' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3638 mFEStack.top()->ReadWord(w, true);
3639 truth doIt = (w == ename);
3640 if (doIt && !res) {
3641 res = DoOnEvent(false);
3642 } else {
3643 // skip
3644 SkipBlock(false);
3648 mChar = old;
3649 return res;
3653 truth game::RunOnEventStr (cfestring &ename, cfestring &str) {
3654 truth res = false;
3655 if (str.GetSize() < 1) return false;
3656 //fprintf(stderr, "=============\n%s=============\n", str.CStr());
3657 TextInput *ifl = new MemTextFile("<memory>", str, &game::GetGlobalValueMap());
3658 ifl->setGetVarCB(game::ldrGetVar);
3659 mFEStack.push(ifl);
3660 festring w;
3661 //fprintf(stderr, "=============\n", str.CStr());
3662 //fprintf(stderr, "event: [%s]\n", ename.CStr());
3663 //fprintf(stderr, "---\n%s---\n", str.CStr());
3664 while (GetWord(w)) {
3665 if (w != "on") ABORT("'on' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3666 mFEStack.top()->ReadWord(w, true);
3667 //fprintf(stderr, "on: [%s]\n", w.CStr());
3668 truth doIt = (w==ename);
3669 if (doIt && !res) {
3670 //fprintf(stderr, " do it\n");
3671 res = DoOnEvent(false);
3672 } else {
3673 // skip
3674 //fprintf(stderr, " skip it\n");
3675 SkipBlock(false);
3678 return res;
3682 truth game::RunOnCharEvent (character *who, cfestring &ename) {
3683 truth res = false;
3684 if (!who) return false;
3685 character *old = mChar;
3686 mChar = who;
3687 res = RunOnEventStr(ename, who->mOnEvents);
3688 if (!res) res = RunOnEventStr(ename, who->GetProtoType()->mOnEvents);
3689 mChar = old;
3690 return res;
3694 truth game::RunOnItemEvent (item *what, cfestring &ename) {
3695 truth res = false;
3696 if (!what) return false;
3697 item *old = mItem;
3698 mItem = what;
3699 res = RunOnEventStr(ename, what->mOnEvents);
3700 if (!res) res = RunOnEventStr(ename, what->GetProtoType()->mOnEvents);
3701 mItem = old;
3702 return res;
3706 festring game::ldrGetVar (TextInput *fl, cfestring &name) {
3707 //fprintf(stderr, "GETVAR: [%s]\n", name.CStr());
3708 if (name == "player_name") {
3709 return game::GetPlayerName();
3711 if (name == "money") {
3712 festring res;
3713 if (!mChar) return "0";
3714 res << mChar->GetMoney();
3715 return res;
3717 if (name == "name") {
3718 if (!mChar) return "";
3719 return mChar->GetAssignedName();
3721 if (name == "team") {
3722 festring res;
3723 if (!mChar) return "";
3724 res << mChar->GetTeam()->GetID();
3725 return res;
3727 if (name == "friendly") {
3728 festring res;
3729 if (!mChar || !PLAYER || mChar->GetRelation(PLAYER) != HOSTILE) return "tan";
3730 return "";
3732 if (name == "hostile") {
3733 festring res;
3734 if (!mChar || !PLAYER) return "";
3735 if (mChar->GetRelation(PLAYER) == HOSTILE) return "tan";
3736 return "";
3738 if (name == "has_item") {
3739 std::vector<FuncArg> args;
3740 ParseFuncArgs("s", args, fl, true);
3742 if (PLAYER) {
3743 itemvector items;
3744 festring s = args[0].sval;
3746 //fprintf(stderr, "looking for [%s]\n", s.CStr());
3747 PLAYER->GetStack()->FillItemVector(items);
3748 for (unsigned int f = 0; f < items.size(); ++f) {
3749 for (uInt c = 0; c < items[f]->GetDataBase()->Alias.Size; ++c) {
3750 //fprintf(stderr, "%u:%u: [%s]\n", f, c, items[f]->GetDataBase()->Alias[c].CStr());
3751 if (s.CompareIgnoreCase(items[f]->GetDataBase()->Alias[c]) == 0) {
3752 //fprintf(stderr, " FOUND!\n");
3753 return "tan";
3757 //fprintf(stderr, "checking equipment...\n");
3758 for (int f = 0; f < PLAYER->GetEquipments(); ++f) {
3759 item *it = PLAYER->GetEquipment(f);
3761 if (it) {
3762 for (uInt c = 0; c < it->GetDataBase()->Alias.Size; ++c) {
3763 //fprintf(stderr, "%u:%u: [%s]\n", f, c, it->GetDataBase()->Alias[c].CStr());
3764 if (s.CompareIgnoreCase(it->GetDataBase()->Alias[c]) == 0) {
3765 //fprintf(stderr, " FOUND!\n");
3766 return "tan";
3772 return "";
3774 //if (name == "type") return mVarType;
3775 ABORT("unknown variable: %s", name.CStr());
3776 return "";
3780 truth game::CheckDropLeftover (item *i) {
3781 if (i->IsBottle() && !ivanconfig::GetAutoDropBottles()) return false;
3782 if (i->IsCan() && !ivanconfig::GetAutoDropCans()) return false;
3783 if (!ivanconfig::GetAutoDropLeftOvers()) return false;
3784 return true;
3788 truth game::RunAllowScriptStr (cfestring &str) {
3789 truth res = true;
3790 if (str.GetSize() < 1) return true;
3791 //fprintf(stderr, "====\n%s\n====\n", str.CStr());
3792 TextInput *ifl = new MemTextFile("<memory>", str, &game::GetGlobalValueMap());
3793 ifl->setGetVarCB(game::ldrGetVar);
3794 mFEStack.push(ifl);
3795 res = DoOnEvent(true, true);
3796 //fprintf(stderr, "mFEStack: %u\n", mFEStack.size());
3797 return res;