some stupid 'fixes' for some segfaults
[k8-i-v-a-n.git] / src / game / game.cpp
blob977794ab66974b69989bce8995e9796af0b327f6
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 #ifdef WIN32
19 # include <unistd.h>
20 # include <windows.h>
21 #endif
23 #include "whandler.h"
24 #include "hscore.h"
25 #include "rawbit.h"
26 #include "message.h"
27 #include "feio.h"
28 #include "team.h"
29 #include "iconf.h"
30 #include "allocate.h"
31 #include "pool.h"
32 #include "god.h"
33 #include "proto.h"
34 #include "stack.h"
35 #include "felist.h"
36 #include "human.h"
37 #include "nonhuman.h"
38 #include "wsquare.h"
39 #include "game.h"
40 #include "graphics.h"
41 #include "bitmap.h"
42 #include "fesave.h"
43 #include "miscitem.h"
44 #include "room.h"
45 #include "materias.h"
46 #include "rain.h"
47 #include "gear.h"
48 #include "fetime.h"
49 #include "balance.h"
50 #include "confdef.h"
51 #include "wmapset.h"
53 #define SAVE_FILE_VERSION 130 // Increment this if changes make savefiles incompatible
54 #define BONE_FILE_VERSION 115 // Increment this if changes make bonefiles incompatible
56 #define LOADED 0
57 #define NEW_GAME 1
58 #define BACK 2
61 std::stack<inputfile *> game::mFEStack;
62 character *game::mChar = 0;
63 ccharacter *game::mActor = 0;
64 ccharacter *game::mSecondActor = 0;
65 item *game::mItem = 0;
66 int game::mResult = 0;
69 int game::CurrentLevelIndex;
70 truth game::InWilderness = false;
71 worldmap* game::WorldMap;
72 area* game::AreaInLoad;
73 square* game::SquareInLoad;
74 dungeon** game::Dungeon;
75 int game::CurrentDungeonIndex;
76 feuLong game::NextCharacterID = 1;
77 feuLong game::NextItemID = 1;
78 feuLong game::NextTrapID = 1;
79 team** game::Team;
80 feuLong game::LOSTick;
81 v2 game::CursorPos(-1, -1);
82 truth game::Zoom;
83 truth game::Generating = false;
84 double game::AveragePlayerArmStrengthExperience;
85 double game::AveragePlayerLegStrengthExperience;
86 double game::AveragePlayerDexterityExperience;
87 double game::AveragePlayerAgilityExperience;
88 int game::Teams;
89 int game::Dungeons;
90 int game::StoryState;
91 /* */
92 int game::MondedrPass;
93 int game::RingOfThieves;
94 int game::Masamune;
95 int game::Muramasa;
96 int game::LoricatusHammer;
97 int game::Liberator;
98 int game::OmmelBloodMission;
99 int game::RegiiTalkState;
100 /* */
101 massacremap game::PlayerMassacreMap;
102 massacremap game::PetMassacreMap;
103 massacremap game::MiscMassacreMap;
104 sLong game::PlayerMassacreAmount = 0;
105 sLong game::PetMassacreAmount = 0;
106 sLong game::MiscMassacreAmount = 0;
107 boneidmap game::BoneItemIDMap;
108 boneidmap game::BoneCharacterIDMap;
109 truth game::TooGreatDangerFoundTruth;
110 itemvectorvector game::ItemDrawVector;
111 charactervector game::CharacterDrawVector;
112 truth game::SumoWrestling;
113 liquid* game::GlobalRainLiquid;
114 v2 game::GlobalRainSpeed;
115 sLong game::GlobalRainTimeModifier;
116 truth game::PlayerSumoChampion;
117 truth game::PlayerSolicitusChampion;
118 feuLong game::SquarePartEmitationTick = 0;
119 sLong game::Turn;
120 truth game::PlayerRunning;
121 character* game::LastPetUnderCursor;
122 charactervector game::PetVector;
123 double game::DangerFound;
124 int game::OldAttribute[ATTRIBUTES];
125 int game::NewAttribute[ATTRIBUTES];
126 int game::LastAttributeChangeTick[ATTRIBUTES];
127 int game::NecroCounter;
128 int game::CursorData;
129 truth game::CausePanicFlag;
131 truth game::Loading = false;
132 truth game::JumpToPlayerBe = false;
133 truth game::InGetCommand = false;
134 character *game::Petrus = 0;
135 time_t game::TimePlayedBeforeLastLoad;
136 time_t game::LastLoad;
137 time_t game::GameBegan;
138 truth game::PlayerHasReceivedAllGodsKnownBonus;
140 festring game::AutoSaveFileName = game::GetSaveDir() + "AutoSave";
141 cchar *const game::Alignment[] = { "L++", "L+", "L", "L-", "N+", "N=", "N-", "C+", "C", "C-", "C--" };
142 god **game::God;
144 cint game::MoveNormalCommandKey[] = { KEY_HOME, KEY_UP, KEY_PAGE_UP, KEY_LEFT, KEY_RIGHT, KEY_END, KEY_DOWN, KEY_PAGE_DOWN, '.' };
145 int game::MoveAbnormalCommandKey[] = { '7','8','9','u','o','j','k','l','.' };
147 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) };
148 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) };
149 cv2 game::BasicMoveVector[] = { v2(-1, 0), v2(1, 0), v2(0, -1), v2(0, 1) };
150 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) };
151 cint game::LargeMoveDirection[] = { 0, 1, 1, 2, 3, 4, 3, 4, 5, 6, 6, 7, 8, 8, 8, 8 };
153 truth game::LOSUpdateRequested = false;
154 uChar ***game::LuxTable = 0;
155 truth game::Running;
156 character *game::Player;
157 v2 game::Camera(0, 0);
158 feuLong game::Tick;
159 gamescript *game::GameScript = 0;
160 valuemap game::GlobalValueMap;
161 dangermap game::DangerMap;
162 int game::NextDangerIDType;
163 int game::NextDangerIDConfigIndex;
164 characteridmap game::CharacterIDMap;
165 itemidmap game::ItemIDMap;
166 trapidmap game::TrapIDMap;
167 truth game::PlayerHurtByExplosion;
168 area *game::CurrentArea;
169 level *game::CurrentLevel;
170 wsquare ***game::CurrentWSquareMap;
171 lsquare ***game::CurrentLSquareMap;
172 festring game::DefaultPolymorphTo;
173 festring game::DefaultSummonMonster;
174 festring game::DefaultWish;
175 festring game::DefaultChangeMaterial;
176 festring game::DefaultDetectMaterial;
177 festring game::DefaultTeam;
178 truth game::WizardMode;
179 int game::SeeWholeMapCheatMode;
180 truth game::GoThroughWallsCheat;
181 int game::QuestMonstersFound;
182 bitmap *game::BusyAnimationCache[32];
183 festring game::PlayerName;
184 feuLong game::EquipmentMemory[MAX_EQUIPMENT_SLOTS];
185 olterrain *game::MonsterPortal;
186 std::vector<v2> game::SpecialCursorPos;
187 std::vector<int> game::SpecialCursorData;
188 cbitmap *game::EnterImage;
189 v2 game::EnterTextDisplacement;
192 // -1: none
193 int game::MoveVectorToDirection (cv2 &mv) {
194 for (int c = 0; c < 9; ++c) if (MoveVector[c] == mv) return c;
195 return -1;
199 char game::GetAbnormalMoveKey (int idx) {
200 if (idx < 0 || idx > 8) return 0;
201 return MoveAbnormalCommandKey[idx];
205 void game::SetAbnormalMoveKey (int idx, char ch) {
206 if (idx >= 0 && idx <= 8) MoveAbnormalCommandKey[idx] = ch;
210 void game::AddCharacterID (character *Char, feuLong ID) {
211 /*k8:??? if (CharacterIDMap.find(ID) != CharacterIDMap.end())
212 int esko = esko = 2;*/
213 CharacterIDMap.insert(std::make_pair(ID, Char));
217 void game::RemoveCharacterID (feuLong ID) {
218 /*k8:??? if (CharacterIDMap.find(ID) == CharacterIDMap.end())
219 int esko = esko = 2;*/
220 CharacterIDMap.erase(CharacterIDMap.find(ID));
224 void game::AddItemID (item *Item, feuLong ID) {
225 /*k8:??? if (ItemIDMap.find(ID) != ItemIDMap.end())
226 int esko = esko = 2;*/
227 ItemIDMap.insert(std::make_pair(ID, Item));
231 void game::RemoveItemID (feuLong ID) {
232 /*k8:??? if(ID && ItemIDMap.find(ID) == ItemIDMap.end())
233 int esko = esko = 2;*/
234 if (ID) ItemIDMap.erase(ItemIDMap.find(ID));
238 void game::UpdateItemID (item *Item, feuLong ID) {
239 /*k8:??? if(ItemIDMap.find(ID) == ItemIDMap.end())
240 int esko = esko = 2;*/
241 ItemIDMap.find(ID)->second = Item;
245 void game::AddTrapID (entity *Trap, feuLong ID) {
246 /*k8:??? if(TrapIDMap.find(ID) != TrapIDMap.end())
247 int esko = esko = 2;*/
248 if (ID) TrapIDMap.insert(std::make_pair(ID, Trap));
252 void game::RemoveTrapID (feuLong ID) {
253 /*k8:??? if(ID && TrapIDMap.find(ID) == TrapIDMap.end())
254 int esko = esko = 2;*/
255 if (ID) TrapIDMap.erase(TrapIDMap.find(ID));
259 void game::UpdateTrapID (entity *Trap, feuLong ID) {
260 /*k8:??? if(TrapIDMap.find(ID) == TrapIDMap.end())
261 int esko = esko = 2;*/
262 TrapIDMap.find(ID)->second = Trap;
266 const dangermap &game::GetDangerMap () { return DangerMap; }
267 void game::ClearItemDrawVector () { ItemDrawVector.clear(); }
268 void game::ClearCharacterDrawVector () { CharacterDrawVector.clear(); }
271 void game::InitScript () {
272 inputfile ScriptFile(GetGameDir()+"Script/dungeon.dat", &GlobalValueMap);
273 GameScript = new gamescript;
274 GameScript->ReadFrom(ScriptFile);
275 { /* additional dungeon files */
276 for (int f = 0; f <= 99; f++) {
277 char bnum[32];
278 sprintf(bnum, "Script/dungeon_%02d.dat", f);
279 inputfile ifl(game::GetGameDir()+bnum, &game::GetGlobalValueMap(), false);
280 if (ifl.IsOpen()) {
281 //fprintf(stderr, "loading: %s\n", bnum+7);
282 GameScript->ReadFrom(ifl);
283 ifl.Close();
287 GameScript->RandomizeLevels();
291 truth game::Init (cfestring &Name) {
292 if (Name.IsEmpty()) {
293 if (ivanconfig::GetDefaultName().IsEmpty()) {
294 PlayerName.Empty();
295 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;
296 } else {
297 PlayerName = ivanconfig::GetDefaultName();
299 } else {
300 PlayerName = Name;
303 #ifndef WIN32
304 mkdir(GetSaveDir().CStr(), S_IRWXU|S_IRWXG);
305 mkdir(GetBoneDir().CStr(), S_IRWXU|S_IRWXG);
306 #else
307 mkdir(GetSaveDir().CStr());
308 mkdir(GetBoneDir().CStr());
309 #endif
311 ::InitPlaces();
312 LOSTick = 2;
313 DangerFound = 0;
314 CausePanicFlag = false;
315 pool::KillBees();
316 //???
317 switch (Load(SaveName(PlayerName))) {
318 case LOADED: {
319 globalwindowhandler::InstallControlLoop(AnimationController);
320 SetIsRunning(true);
321 SetForceJumpToPlayerBe(true);
322 GetCurrentArea()->SendNewDrawRequest();
323 SendLOSUpdateRequest();
324 ADD_MESSAGE("Game loaded successfully.");
325 } return true;
326 case NEW_GAME: {
327 iosystem::TextScreen(CONST_S(
328 "You couldn't possibly have guessed this day would differ from any other.\n"
329 "It began just as always. You woke up at dawn and drove off the giant spider\n"
330 "resting on your face. On your way to work you had serious trouble avoiding\n"
331 "the lions and pythons roaming wild around the village. After getting kicked\n"
332 "by colony masters for being late you performed your twelve-hour routine of\n"
333 "climbing trees, gathering bananas, climbing trees, gathering bananas, chasing\n"
334 "monkeys that stole the first gathered bananas, carrying bananas to the village\n"
335 "and trying to look happy when real food was distributed.\n\n"
336 "Finally you were about to enjoy your free time by taking a quick dip in the\n"
337 "nearby crocodile bay. However, at this point something unusual happened.\n"
338 "You were summoned to the mansion of Richel Decos, the viceroy of the\n"
339 "colony, and were led directly to him."));
340 iosystem::TextScreen(CONST_S(
341 "\"I have a task for you, citizen\", said the viceroy picking his golden\n"
342 "teeth, \"The market price of bananas has taken a deep dive and yet the\n"
343 "central government is about to raise taxes. I have sent appeals to high\n"
344 "priest Petrus but received no response. I fear my enemies in Attnam are\n"
345 "plotting against me and intercepting my messages before they reach him!\"\n\n"
346 "\"That is why you must travel to Attnam with a letter I'll give you and\n"
347 "deliver it to Petrus directly. Alas, you somehow have to cross the sea\n"
348 "between. Because it's winter, all Attnamese ships are trapped by ice and\n"
349 "I have none. Therefore you must venture through the small underwater tunnel\n"
350 "connecting our islands. It is infested with monsters, but since you have\n"
351 "stayed alive here so long, the trip will surely cause you no trouble.\"\n\n"
352 "You have never been so happy! According to the mansion's traveling\n"
353 "brochures, Attnam is a peaceful but bustling world city on a beautiful\n"
354 "snowy fell surrounded by frozen lakes glittering in the arctic sun just\n"
355 "like the diamonds of the imperial treasury. Not that you would believe a\n"
356 "word. The point is that tomorrow you can finally forget your home and\n"
357 "face the untold adventures ahead."));
358 pool::RemoveEverything(); // memory leak!
359 CurrentLevel = 0;
360 globalwindowhandler::InstallControlLoop(AnimationController);
361 LOSTick = 2;
362 DangerFound = 0;
363 CausePanicFlag = false;
364 SetIsRunning(true);
365 InWilderness = true;
366 iosystem::TextScreen(CONST_S("Generating game...\n\nThis may take some time, please wait."), ZERO_V2, WHITE, false, true, &BusyAnimation);
367 igraph::CreateBackGround(GRAY_FRACTAL);
368 NextCharacterID = 1;
369 NextItemID = 1;
370 NextTrapID = 1;
371 InitScript();
372 CreateTeams();
373 CreateGods();
374 SetPlayer(playerkind::Spawn());
375 Player->SetAssignedName(PlayerName);
376 Player->SetTeam(GetTeam(PLAYER_TEAM));
377 Player->SetNP(SATIATED_LEVEL);
378 for (int c = 0; c < ATTRIBUTES; ++c) {
379 if (c != ENDURANCE) Player->EditAttribute(c, (RAND()&1)-(RAND()&1));
380 Player->EditExperience(c, 500, 1<<11);
382 Player->SetMoney(Player->GetMoney()+RAND()%11);
383 GetTeam(0)->SetLeader(Player);
384 InitDangerMap();
386 pool::KillBees();
387 if (Player->IsEnabled()) { Player->Disable(); Player->Enable(); }
389 Petrus = 0;
390 InitDungeons();
391 SetCurrentArea(WorldMap = new worldmap(128, 128));
392 CurrentWSquareMap = WorldMap->GetMap();
393 WorldMap->Generate();
394 UpdateCamera();
395 SendLOSUpdateRequest();
396 Tick = 0;
397 Turn = 0;
398 InitPlayerAttributeAverage();
399 StoryState = 0;
400 /* */
401 MondedrPass = 0;
402 RingOfThieves = 0;
403 Masamune = 0;
404 Muramasa = 0;
405 LoricatusHammer = 0;
406 Liberator = 0;
407 OmmelBloodMission = 0;
408 RegiiTalkState = 0;
409 /* */
410 PlayerMassacreMap.clear();
411 PetMassacreMap.clear();
412 MiscMassacreMap.clear();
413 PlayerMassacreAmount = PetMassacreAmount = MiscMassacreAmount = 0;
414 DefaultPolymorphTo.Empty();
415 DefaultSummonMonster.Empty();
416 DefaultWish.Empty();
417 DefaultChangeMaterial.Empty();
418 DefaultDetectMaterial.Empty();
419 DefaultTeam.Empty();
420 Player->GetStack()->AddItem(encryptedscroll::Spawn());
421 if (ivanconfig::GetDefaultPetName() != "_none_") {
422 character *Doggie = dog::Spawn();
423 Doggie->SetTeam(GetTeam(PLAYER_TEAM));
424 GetWorldMap()->GetPlayerGroup().push_back(Doggie);
425 Doggie->SetAssignedName(ivanconfig::GetDefaultPetName());
427 WizardMode = false;
428 SeeWholeMapCheatMode = MAP_HIDDEN;
429 GoThroughWallsCheat = false;
430 SumoWrestling = false;
431 GlobalRainTimeModifier = 2048-(RAND()&4095);
432 PlayerSumoChampion = false;
433 PlayerSolicitusChampion = false;
434 protosystem::InitCharacterDataBaseFlags();
435 memset(EquipmentMemory, 0, sizeof(EquipmentMemory));
436 PlayerRunning = false;
437 InitAttributeMemory();
438 NecroCounter = 0;
439 GameBegan = time(0);
440 LastLoad = time(0);
441 TimePlayedBeforeLastLoad = time::GetZeroTime();
442 /*k8: damn! seems that this is field, not local! bool PlayerHasReceivedAllGodsKnownBonus = false; */
443 PlayerHasReceivedAllGodsKnownBonus = false;
444 ADD_MESSAGE("You commence your journey to Attnam. Use direction keys to move, '>' to enter an area and '?' to view other commands.");
445 game::ClearEventData();
446 RunOnEvent("game_start");
447 if (IsXMas()) {
448 item *Present = banana::Spawn();
449 Player->GetStack()->AddItem(Present);
450 ADD_MESSAGE("Atavus is happy today! He gives you %s.", Present->CHAR_NAME(INDEFINITE));
452 } return true;
453 default: return false;
458 void game::DeInit () {
459 pool::BurnHell();
460 delete WorldMap;
461 WorldMap = 0;
462 if (Dungeon) {
463 for (int c = 1; c < Dungeons; ++c) delete Dungeon[c];
464 delete [] Dungeon;
465 Dungeon = 0;
467 if (God) {
468 for (int c = 1; c <= GODS; ++c) delete God[c]; // sorry, Valpuri!
469 delete [] God;
470 God = 0;
472 if (Team) {
473 for (int c = 0; c < Teams; ++c) delete Team[c];
474 delete [] Team;
475 Team = 0;
477 delete GameScript;
478 GameScript = 0;
479 msgsystem::Format();
480 DangerMap.clear();
484 void game::Run () {
485 for (;;) {
486 if (!InWilderness) {
487 /* Temporary places */
488 static int Counter = 0;
489 if (++Counter == 10) {
490 CurrentLevel->GenerateMonsters();
491 Counter = 0;
493 if (CurrentDungeonIndex == ELPURI_CAVE && CurrentLevelIndex == ZOMBIE_LEVEL && !RAND_N(1000+NecroCounter)) {
494 character *Char = necromancer::Spawn(RAND_N(4) ? APPRENTICE_NECROMANCER : MASTER_NECROMANCER);
495 v2 Pos;
496 for (int c2 = 0; c2 < 30; ++c2) {
497 Pos = GetCurrentLevel()->GetRandomSquare(Char);
498 if (abs(int(Pos.X)-Player->GetPos().X) > 20 || abs(int(Pos.Y)-Player->GetPos().Y) > 20) break;
500 if (Pos != ERROR_V2) {
501 Char->SetTeam(GetTeam(MONSTER_TEAM));
502 Char->PutTo(Pos);
503 Char->SetGenerationDanger(GetCurrentLevel()->GetDifficulty());
504 Char->SignalGeneration();
505 Char->SignalNaturalGeneration();
506 ivantime Time;
507 GetTime(Time);
508 int Modifier = Time.Day - EDIT_ATTRIBUTE_DAY_MIN;
509 if (Modifier > 0) Char->EditAllAttributes(Modifier >> EDIT_ATTRIBUTE_DAY_SHIFT);
510 NecroCounter += 50;
511 } else {
512 delete Char;
513 //Char->SendToHell(); // k8:equipment
517 if (!(GetTick() % 1000)) CurrentLevel->CheckSunLight();
519 if ((CurrentDungeonIndex == NEW_ATTNAM || CurrentDungeonIndex == ATTNAM) && CurrentLevelIndex == 0) {
520 sLong OldVolume = GlobalRainLiquid->GetVolume();
521 sLong NewVolume = Max(sLong(sin((Tick+GlobalRainTimeModifier)*0.0003)*300-150), 0);
522 if (NewVolume && !OldVolume) CurrentLevel->EnableGlobalRain();
523 else if(!NewVolume && OldVolume) CurrentLevel->DisableGlobalRain();
524 GlobalRainLiquid->SetVolumeNoSignals(NewVolume);
527 item *Item;
528 if (!RAND_N(2)) Item = wand::Spawn(1 + RAND_N(12));
529 else if(!RAND_N(2)) {
530 Item = beartrap::Spawn();
531 Item->SetIsActive(true);
532 Item->SetTeam(MONSTER_TEAM);
533 } else if(!RAND_N(2)) {
534 Item = mine::Spawn();
535 Item->SetIsActive(true);
536 Item->SetTeam(MONSTER_TEAM);
537 } else Item = holybanana::Spawn();
538 CurrentLevel->GetLSquare(CurrentLevel->GetRandomSquare())->AddItem(Item);
541 if(!RAND_N(10)) {
542 character *Char = protosystem::CreateMonster(0, 1000000);
543 Char->ChangeTeam(GetTeam(RAND() % Teams));
544 Char->PutTo(CurrentLevel->GetRandomSquare(Char));
547 if (!RAND_N(5)) {
548 character *Char;
549 if (!RAND_N(5)) Char = spider::Spawn(GIANT);
550 else if (!RAND_N(5)) Char = darkmage::Spawn(1 + RAND_N(4));
551 else if (!RAND_N(5)) Char = necromancer::Spawn(1 + RAND_N(2));
552 else if (!RAND_N(5)) Char = chameleon::Spawn();
553 else if (!RAND_N(5)) Char = kamikazedwarf::Spawn(1 + RAND_N(GODS));
554 else if (!RAND_N(5)) Char = mommo::Spawn(1 + RAND_N(2));
555 else if (!RAND_N(3)) Char = bunny::Spawn(RAND_2 ? ADULT_MALE : ADULT_FEMALE);
556 else if (!RAND_N(3)) Char = eddy::Spawn();
557 else if (!RAND_N(3)) Char = magicmushroom::Spawn();
558 else if (!RAND_N(5)) Char = mushroom::Spawn();
559 else if (!RAND_N(3)) Char = blinkdog::Spawn();
560 else if (!RAND_N(5)) Char = tourist::Spawn(1 + RAND_N(3));
561 else if (!RAND_N(5)) Char = hattifattener::Spawn();
562 else if (!RAND_N(5)) Char = genetrixvesana::Spawn();
563 else if (!RAND_N(5)) Char = skunk::Spawn();
564 else if (!RAND_N(5)) Char = ennerbeast::Spawn();
565 else if (!RAND_N(5)) Char = werewolfhuman::Spawn();
566 else if (!RAND_N(5)) Char = unicorn::Spawn(1 + RAND_N(3));
567 else if (!RAND_N(5)) Char = floatingeye::Spawn();
568 else if (!RAND_N(5)) Char = zombie::Spawn();
569 else if (!RAND_N(5)) Char = magpie::Spawn();
570 else if (!RAND_N(5)) Char = elpuri::Spawn();
571 else if (!RAND_N(5)) Char = vladimir::Spawn();
572 else if (!RAND_N(5)) Char = billswill::Spawn();
573 else if (!RAND_N(5)) Char = ghost::Spawn();
574 else if (!RAND_N(5)) Char = dolphin::Spawn();
575 else if (!RAND_N(5)) Char = cossack::Spawn();
576 else Char = invisiblestalker::Spawn();
577 Char->SetTeam(GetTeam(RAND() % Teams));
578 Char->PutTo(CurrentLevel->GetRandomSquare(Char));
584 try {
585 pool::Be();
586 pool::BurnHell();
587 IncreaseTick();
588 ApplyDivineTick();
589 } catch (quitrequest) {
590 break;
591 } catch (areachangerequest) {
597 void game::InitLuxTable () {
598 if (!LuxTable) {
599 Alloc3D(LuxTable, 256, 33, 33);
600 for (int c = 0; c < 0x100; ++c)
601 for (int x = 0; x < 33; ++x)
602 for (int y = 0; y < 33; ++y) {
603 int X = x-16, Y = y-16;
604 LuxTable[c][x][y] = int(c/(double(X*X+Y*Y)/128+1));
606 atexit(DeInitLuxTable);
611 void game::DeInitLuxTable () {
612 delete [] LuxTable;
613 LuxTable = 0;
617 void game::UpdateCameraX () {
618 UpdateCameraX(Player->GetPos().X);
622 void game::UpdateCameraY () {
623 UpdateCameraY(Player->GetPos().Y);
627 void game::UpdateCameraX (int X) {
628 UpdateCameraCoordinate(Camera.X, X, GetCurrentArea()->GetXSize(), GetScreenXSize());
632 void game::UpdateCameraY (int Y) {
633 UpdateCameraCoordinate(Camera.Y, Y, GetCurrentArea()->GetYSize(), GetScreenYSize());
637 void game::UpdateCameraCoordinate (int &Coordinate, int Center, int Size, int ScreenSize) {
638 int OldCoordinate = Coordinate;
639 if (Size < ScreenSize) Coordinate = (Size-ScreenSize)>>1;
640 else if(Center < ScreenSize>>1) Coordinate = 0;
641 else if(Center > Size-(ScreenSize>>1)) Coordinate = Size-ScreenSize;
642 else Coordinate = Center-(ScreenSize>>1);
643 if (Coordinate != OldCoordinate) GetCurrentArea()->SendNewDrawRequest();
647 cchar *game::Insult () {
648 static const char *insults[19] = {
649 "moron",
650 "silly",
651 "idiot",
652 "airhead",
653 "jerk",
654 "dork",
655 "Mr. Mole",
656 "navastater",
657 "potatoes-for-eyes",
658 "lamer",
659 "mommo-for-brains",
660 "pinhead",
661 "stupid-headed person",
662 "software abuser",
663 "loser",
664 "peaballs",
665 "person-with-problems",
666 "unimportant user",
667 "hugger-mugger"
669 int n = RAND_N(18);
670 if (n < 0 || n > 18) n = 18;
671 return insults[n];
675 /* DefaultAnswer = REQUIRES_ANSWER the question requires an answer */
676 truth game::TruthQuestion (cfestring &String, int DefaultAnswer, int OtherKeyForTrue) {
677 if (DefaultAnswer == NO) DefaultAnswer = 'n';
678 else if (DefaultAnswer == YES) DefaultAnswer = 'y';
679 else if (DefaultAnswer != REQUIRES_ANSWER) ABORT("Illegal TruthQuestion DefaultAnswer send!");
680 int FromKeyQuestion = KeyQuestion(String, DefaultAnswer, 9, 'y', 'Y', 'n', 'N', 't', 'T', 'o', 'O', OtherKeyForTrue);
681 return
682 FromKeyQuestion == 'y' || FromKeyQuestion == 'Y' ||
683 FromKeyQuestion == 't' || FromKeyQuestion == 'T' ||
684 FromKeyQuestion == OtherKeyForTrue;
688 void game::DrawEverything () {
689 DrawEverythingNoBlit();
690 graphics::BlitDBToScreen();
694 truth game::OnScreen (v2 Pos) {
695 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();
699 void game::DrawEverythingNoBlit (truth AnimationDraw) {
700 if (LOSUpdateRequested && Player->IsEnabled()) {
701 if (!IsInWilderness()) GetCurrentLevel()->UpdateLOS(); else GetWorldMap()->UpdateLOS();
704 if (OnScreen(CursorPos)) {
705 if (!IsInWilderness() || CurrentWSquareMap[CursorPos.X][CursorPos.Y]->GetLastSeen() || GetSeeWholeMapCheatMode())
706 CurrentArea->GetSquare(CursorPos)->SendStrongNewDrawRequest();
707 else
708 DOUBLE_BUFFER->Fill(CalculateScreenCoordinates(CursorPos), TILE_V2, 0);
711 for (unsigned int c = 0; c < SpecialCursorPos.size(); ++c) {
712 if (OnScreen(SpecialCursorPos[c])) CurrentArea->GetSquare(SpecialCursorPos[c])->SendStrongNewDrawRequest();
715 globalwindowhandler::UpdateTick();
716 GetCurrentArea()->Draw(AnimationDraw);
717 Player->DrawPanel(AnimationDraw);
719 if (!AnimationDraw) msgsystem::Draw();
721 if (OnScreen(CursorPos)) {
722 v2 ScreenCoord = CalculateScreenCoordinates(CursorPos);
723 blitdata B = {
724 DOUBLE_BUFFER,
725 { 0, 0 },
726 { ScreenCoord.X, ScreenCoord.Y },
727 { TILE_SIZE, TILE_SIZE },
728 { 0 },
729 TRANSPARENT_COLOR,
730 ALLOW_ANIMATE|ALLOW_ALPHA
733 if (!IsInWilderness() && !GetSeeWholeMapCheatMode()) {
734 lsquare *Square = CurrentLSquareMap[CursorPos.X][CursorPos.Y];
735 if (Square->GetLastSeen() != GetLOSTick()) Square->DrawMemorized(B);
738 if (DoZoom()) {
739 B.Src = B.Dest;
740 B.Dest.X = RES.X - 96;
741 B.Dest.Y = RES.Y - 96;
742 B.Stretch = 5;
743 DOUBLE_BUFFER->StretchBlit(B);
746 igraph::DrawCursor(ScreenCoord, CursorData);
749 if (Player->IsEnabled()) {
750 if (Player->IsSmall()) {
751 v2 Pos = Player->GetPos();
752 if (OnScreen(Pos)) {
753 v2 ScreenCoord = CalculateScreenCoordinates(Pos);
754 igraph::DrawCursor(ScreenCoord, Player->GetCursorData());
756 } else {
757 for (int f = 0; f < Player->GetSquaresUnder(); ++f) {
758 v2 Pos = Player->GetPos(f);
759 if (OnScreen(Pos)) {
760 v2 ScreenCoord = CalculateScreenCoordinates(Pos);
761 igraph::DrawCursor(ScreenCoord, Player->GetCursorData()|CURSOR_BIG, f);
767 for (unsigned int c = 0; c < SpecialCursorPos.size(); ++c) {
768 if (OnScreen(SpecialCursorPos[c])) {
769 v2 ScreenCoord = CalculateScreenCoordinates(SpecialCursorPos[c]);
770 igraph::DrawCursor(ScreenCoord, SpecialCursorData[c]);
771 GetCurrentArea()->GetSquare(SpecialCursorPos[c])->SendStrongNewDrawRequest();
777 truth game::Save (cfestring &SaveName) {
778 if (!GetCurrentArea()->GetSquare(Player->GetPos())->GetCharacter()) {
779 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);
780 return false;
782 DrawEverythingNoBlit();
783 #if defined(SGAME_SHOTS_IPU) || !defined(HAVE_IMLIB2)
784 DOUBLE_BUFFER->SaveScaledIPU(SaveName+".ipu", 0.8); //640; 320
785 #else
786 DOUBLE_BUFFER->SaveScaledPNG(SaveName+".png", 0.8); //640; 320
787 #endif
788 outputfile SaveFile(SaveName+".sav", ivanconfig::GetUseMaximumCompression());
789 SaveFile << int(SAVE_FILE_VERSION);
790 SaveFile << GameScript << CurrentDungeonIndex << CurrentLevelIndex << Camera;
791 SaveFile << WizardMode << SeeWholeMapCheatMode << GoThroughWallsCheat;
792 SaveFile << Tick << Turn << InWilderness << NextCharacterID << NextItemID << NextTrapID << NecroCounter;
793 SaveFile << SumoWrestling << PlayerSumoChampion << GlobalRainTimeModifier;
794 SaveFile << PlayerSolicitusChampion;
795 sLong Seed = RAND();
796 femath::SetSeed(Seed);
797 SaveFile << Seed;
798 SaveFile << AveragePlayerArmStrengthExperience;
799 SaveFile << AveragePlayerLegStrengthExperience;
800 SaveFile << AveragePlayerDexterityExperience;
801 SaveFile << AveragePlayerAgilityExperience;
802 SaveFile << Teams << Dungeons << StoryState << PlayerRunning;
803 SaveFile << MondedrPass << RingOfThieves << Masamune << Muramasa << LoricatusHammer << Liberator;
804 SaveFile << OmmelBloodMission << RegiiTalkState;
805 SaveFile << PlayerMassacreMap << PetMassacreMap << MiscMassacreMap;
806 SaveFile << PlayerMassacreAmount << PetMassacreAmount << MiscMassacreAmount;
807 SaveArray(SaveFile, EquipmentMemory, MAX_EQUIPMENT_SLOTS);
808 int c;
809 for (c = 0; c < ATTRIBUTES; ++c) SaveFile << OldAttribute[c] << NewAttribute[c] << LastAttributeChangeTick[c];
810 for (c = 1; c < Dungeons; ++c) SaveFile << Dungeon[c];
811 for (c = 1; c <= GODS; ++c) SaveFile << God[c];
812 for (c = 0; c < Teams; ++c) SaveFile << Team[c];
813 if (InWilderness) {
814 SaveWorldMap(SaveName, false);
815 } else {
816 GetCurrentDungeon()->SaveLevel(SaveName, CurrentLevelIndex, false);
818 SaveFile << Player->GetPos() << PlayerName;
819 msgsystem::Save(SaveFile);
820 SaveFile << DangerMap << NextDangerIDType << NextDangerIDConfigIndex;
821 SaveFile << DefaultPolymorphTo << DefaultSummonMonster;
822 SaveFile << DefaultWish << DefaultChangeMaterial << DefaultDetectMaterial << DefaultTeam;
823 SaveFile << GetTimeSpent();
824 /* or in more readable format: time() - LastLoad + TimeAtLastLoad */
825 SaveFile << PlayerHasReceivedAllGodsKnownBonus;
826 protosystem::SaveCharacterDataBaseFlags(SaveFile);
827 return true;
831 int game::Load (cfestring &SaveName) {
832 inputfile SaveFile(SaveName+".sav", 0, false);
833 if (!SaveFile.IsOpen()) return NEW_GAME;
834 int Version;
835 SaveFile >> Version;
836 if (Version != SAVE_FILE_VERSION) {
837 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)) {
838 return NEW_GAME;
839 } else {
840 return BACK;
843 SaveFile >> GameScript >> CurrentDungeonIndex >> CurrentLevelIndex >> Camera;
844 SaveFile >> WizardMode >> SeeWholeMapCheatMode >> GoThroughWallsCheat;
845 SaveFile >> Tick >> Turn >> InWilderness >> NextCharacterID >> NextItemID >> NextTrapID >> NecroCounter;
846 SaveFile >> SumoWrestling >> PlayerSumoChampion >> GlobalRainTimeModifier;
847 SaveFile >> PlayerSolicitusChampion;
848 femath::SetSeed(ReadType<sLong>(SaveFile));
849 SaveFile >> AveragePlayerArmStrengthExperience;
850 SaveFile >> AveragePlayerLegStrengthExperience;
851 SaveFile >> AveragePlayerDexterityExperience;
852 SaveFile >> AveragePlayerAgilityExperience;
853 SaveFile >> Teams >> Dungeons >> StoryState >> PlayerRunning;
854 SaveFile >> MondedrPass >> RingOfThieves >> Masamune >> Muramasa >> LoricatusHammer >> Liberator;
855 SaveFile >> OmmelBloodMission >> RegiiTalkState;
856 SaveFile >> PlayerMassacreMap >> PetMassacreMap >> MiscMassacreMap;
857 SaveFile >> PlayerMassacreAmount >> PetMassacreAmount >> MiscMassacreAmount;
858 LoadArray(SaveFile, EquipmentMemory, MAX_EQUIPMENT_SLOTS);
859 for (int c = 0; c < ATTRIBUTES; ++c) SaveFile >> OldAttribute[c] >> NewAttribute[c] >> LastAttributeChangeTick[c];
860 Dungeon = new dungeon*[Dungeons];
861 Dungeon[0] = 0;
862 for (int c = 1; c < Dungeons; ++c) SaveFile >> Dungeon[c];
863 God = new god*[GODS+1];
864 God[0] = 0;
865 for (int c = 1; c <= GODS; ++c) SaveFile >> God[c];
866 Team = new team*[Teams];
867 for (int c = 0; c < Teams; ++c) SaveFile >> Team[c];
868 if (InWilderness) {
869 SetCurrentArea(LoadWorldMap(SaveName));
870 CurrentWSquareMap = WorldMap->GetMap();
871 igraph::CreateBackGround(GRAY_FRACTAL);
872 } else {
873 SetCurrentArea(CurrentLevel = GetCurrentDungeon()->LoadLevel(SaveName, CurrentLevelIndex));
874 CurrentLSquareMap = CurrentLevel->GetMap();
875 igraph::CreateBackGround(*CurrentLevel->GetLevelScript()->GetBackGroundType());
877 v2 Pos;
878 SaveFile >> Pos >> PlayerName;
879 SetPlayer(GetCurrentArea()->GetSquare(Pos)->GetCharacter());
880 if (!PLAYER) {
881 DeInit();
882 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)) {
883 return NEW_GAME;
884 } else {
885 return BACK;
888 msgsystem::Load(SaveFile);
889 SaveFile >> DangerMap >> NextDangerIDType >> NextDangerIDConfigIndex;
890 SaveFile >> DefaultPolymorphTo >> DefaultSummonMonster;
891 SaveFile >> DefaultWish >> DefaultChangeMaterial >> DefaultDetectMaterial >> DefaultTeam;
892 SaveFile >> TimePlayedBeforeLastLoad;
893 SaveFile >> PlayerHasReceivedAllGodsKnownBonus;
894 LastLoad = time(0);
895 protosystem::LoadCharacterDataBaseFlags(SaveFile);
896 return LOADED;
900 festring game::SaveName (cfestring &Base) {
901 festring SaveName = GetSaveDir();
902 if (!Base.GetSize()) SaveName << PlayerName; else SaveName << Base;
903 for (festring::sizetype c = 0; c < SaveName.GetSize(); ++c) if (SaveName[c] == ' ') SaveName[c] = '_';
904 return SaveName;
908 int game::GetMoveCommandKeyBetweenPoints (v2 A, v2 B) {
909 for (int c = 0; c < EXTENDED_DIRECTION_COMMAND_KEYS; ++c) {
910 if ((A + GetMoveVector(c)) == B) return GetMoveCommandKey(c);
912 return DIR_ERROR;
916 void game::ApplyDivineTick () {
917 for (int c = 1; c <= GODS; ++c) GetGod(c)->ApplyDivineTick();
921 void game::ApplyDivineAlignmentBonuses (god *CompareTarget, int Multiplier, truth Good) {
922 for (int c = 1; c <= GODS; ++c) if (GetGod(c) != CompareTarget) GetGod(c)->AdjustRelation(CompareTarget, Multiplier, Good);
926 v2 game::GetDirectionVectorForKey (int Key) {
927 if (Key == KEY_NUMPAD_5 || Key == '.') return ZERO_V2; /* k8: '.' */
928 for (int c = 0; c < EXTENDED_DIRECTION_COMMAND_KEYS; ++c) if (Key == GetMoveCommandKey(c)) return GetMoveVector(c);
929 return ERROR_V2;
933 double game::GetMinDifficulty () {
934 double Base = CurrentLevel->GetDifficulty()*0.2;
935 sLong MultiplierExponent = 0;
936 ivantime Time;
937 GetTime(Time);
938 int Modifier = Time.Day-DANGER_PLUS_DAY_MIN;
939 if (Modifier > 0) Base += DANGER_PLUS_MULTIPLIER * Modifier;
940 for (;;) {
941 int Dice = RAND()%25;
942 if (Dice < 5 && MultiplierExponent > -3) {
943 Base /= 3;
944 --MultiplierExponent;
945 continue;
947 if (Dice >= 20 && MultiplierExponent < 3) {
948 Base *= 3;
949 ++MultiplierExponent;
950 continue;
952 return Base;
957 void game::ShowLevelMessage () {
958 if (CurrentLevel->GetLevelMessage().GetSize()) ADD_MESSAGE("%s", CurrentLevel->GetLevelMessage().CStr());
959 CurrentLevel->SetLevelMessage("");
963 int game::DirectionQuestion (cfestring &Topic, truth RequireAnswer, truth AcceptYourself) {
964 for (;;) {
965 int Key = AskForKeyPress(Topic);
966 if (AcceptYourself && (Key == '.' || Key == KEY_NUMPAD_5)) return YOURSELF; //k8
967 for (int c = 0; c < DIRECTION_COMMAND_KEYS; ++c) if (Key == GetMoveCommandKey(c)) return c;
968 if (!RequireAnswer) return DIR_ERROR;
973 void game::RemoveSaves (truth RealSavesAlso) {
974 if (RealSavesAlso) {
975 remove(festring(SaveName()+".sav").CStr());
976 remove(festring(SaveName()+".wm").CStr());
977 remove(festring(SaveName()+".png").CStr());
978 remove(festring(SaveName()+".ipu").CStr());
980 remove(festring(AutoSaveFileName+".sav").CStr());
981 remove(festring(AutoSaveFileName+".wm").CStr());
982 remove(festring(AutoSaveFileName+".png").CStr());
983 remove(festring(AutoSaveFileName+".ipu").CStr());
984 festring File;
985 for (int i = 1; i < Dungeons; ++i) {
986 for (int c = 0; c < GetDungeon(i)->GetLevels(); ++c) {
987 /* This looks very odd. And it is very odd.
988 * Indeed, gcc is very odd to not compile this correctly with -O3
989 * if it is written in a less odd way. */
990 File = SaveName()+'.'+i;
991 File << c;
992 if (RealSavesAlso) remove(File.CStr());
993 File = AutoSaveFileName+'.'+i;
994 File << c;
995 remove(File.CStr());
1001 void game::SetPlayer (character *NP) {
1002 Player = NP;
1003 if (Player) Player->AddFlags(C_PLAYER);
1007 void game::InitDungeons () {
1008 Dungeons = *GetGameScript()->GetDungeons()+1;
1009 //fprintf(stderr, "dungeon count: %d\n", Dungeons);
1010 Dungeon = new dungeon *[Dungeons];
1011 Dungeon[0] = 0;
1012 for (int c = 1; c < Dungeons; ++c) {
1013 Dungeon[c] = new dungeon(c);
1014 Dungeon[c]->SetIndex(c);
1019 void game::DoEvilDeed (int Amount) {
1020 if (!Amount) return;
1021 for (int c = 1; c <= GODS; ++c) {
1022 int Change = Amount-Amount*GetGod(c)->GetAlignment()/5;
1023 if (!IsInWilderness() && Player->GetLSquareUnder()->GetDivineMaster() == c) {
1024 if (GetGod(c)->GetRelation()-(Change << 1) < -750) {
1025 if (GetGod(c)->GetRelation() > -750) GetGod(c)->SetRelation(-750);
1026 } else if (GetGod(c)->GetRelation()-(Change << 1) > 750) {
1027 if (GetGod(c)->GetRelation() < 750) GetGod(c)->SetRelation(750);
1028 } else GetGod(c)->SetRelation(GetGod(c)->GetRelation()-(Change << 1));
1029 } else {
1030 if(GetGod(c)->GetRelation()-Change < -500) {
1031 if (GetGod(c)->GetRelation() > -500) GetGod(c)->SetRelation(-500);
1032 } else if (GetGod(c)->GetRelation()-Change > 500) {
1033 if (GetGod(c)->GetRelation() < 500) GetGod(c)->SetRelation(500);
1034 } else GetGod(c)->SetRelation(GetGod(c)->GetRelation() - Change);
1040 void game::SaveWorldMap (cfestring &SaveName, truth DeleteAfterwards) {
1041 outputfile SaveFile(SaveName+".wm", ivanconfig::GetUseMaximumCompression());
1042 SaveFile << WorldMap;
1043 if (DeleteAfterwards) {
1044 delete WorldMap;
1045 WorldMap = 0;
1050 worldmap *game::LoadWorldMap (cfestring &SaveName) {
1051 inputfile SaveFile(SaveName+".wm");
1052 SaveFile >> WorldMap;
1053 return WorldMap;
1057 void game::Hostility (team *Attacker, team *Defender) {
1058 for (int c = 0; c < Teams; ++c) {
1059 if (GetTeam(c) != Attacker && GetTeam(c) != Defender &&
1060 GetTeam(c)->GetRelation(Defender) == FRIEND &&
1061 c != NEW_ATTNAM_TEAM && c != TOURIST_GUIDE_TEAM) // gum solution
1062 GetTeam(c)->SetRelation(Attacker, HOSTILE);
1067 void game::CreateTeams () {
1068 Teams = *GetGameScript()->GetTeams();
1069 //fprintf(stderr, "team count: %d\n", Teams);
1070 Team = new team*[Teams];
1071 for (int c = 0; c < Teams; ++c) {
1072 Team[c] = new team(c);
1073 for (int i = 0; i < c; ++i) Team[i]->SetRelation(Team[c], UNCARING);
1075 for (int c = 0; c < Teams; ++c) if (c != MONSTER_TEAM) Team[MONSTER_TEAM]->SetRelation(Team[c], HOSTILE);
1076 const std::list<std::pair<int, teamscript> >& TeamScript = GetGameScript()->GetTeam();
1077 for (std::list<std::pair<int, teamscript> >::const_iterator i = TeamScript.begin(); i != TeamScript.end(); ++i) {
1078 for (uInt c = 0; c < i->second.GetRelation().size(); ++c) {
1079 GetTeam(i->second.GetRelation()[c].first)->SetRelation(GetTeam(i->first), i->second.GetRelation()[c].second);
1081 cint *KillEvilness = i->second.GetKillEvilness();
1082 if (KillEvilness) GetTeam(i->first)->SetKillEvilness(*KillEvilness);
1083 if (i->second.GetName()) GetTeam(i->first)->SetName(*i->second.GetName());
1088 team *game::FindTeam (cfestring &name) {
1089 for (int c = 0; c < Teams; ++c) {
1090 if (Team[c]->GetName().CompareIgnoreCase(name) == 0) return Team[c];
1092 return 0;
1096 /* v2 Pos should be removed from xxxQuestion()s? */
1097 festring game::StringQuestion (cfestring &Topic, col16 Color, festring::sizetype MinLetters, festring::sizetype MaxLetters, truth AllowExit, stringkeyhandler KeyHandler) {
1098 DrawEverythingNoBlit();
1099 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23)); // pos may be incorrect!
1100 festring Return;
1101 iosystem::StringQuestion(Return, Topic, v2(16, 6), Color, MinLetters, MaxLetters, false, AllowExit, KeyHandler);
1102 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1103 return Return;
1107 sLong game::NumberQuestion (cfestring &Topic, col16 Color, truth ReturnZeroOnEsc) {
1108 DrawEverythingNoBlit();
1109 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1110 sLong Return = iosystem::NumberQuestion(Topic, v2(16, 6), Color, false, ReturnZeroOnEsc);
1111 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1112 return Return;
1116 sLong game::ScrollBarQuestion (cfestring &Topic, sLong BeginValue, sLong Step, sLong Min, sLong Max, sLong AbortValue, col16 TopicColor, col16 Color1, col16 Color2, void (*Handler)(sLong)) {
1117 DrawEverythingNoBlit();
1118 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1119 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);
1120 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1121 return Return;
1125 feuLong game::IncreaseLOSTick () {
1126 if (LOSTick != 0xFE) return LOSTick += 2;
1127 CurrentLevel->InitLastSeen();
1128 return LOSTick = 4;
1132 void game::UpdateCamera () {
1133 UpdateCameraX();
1134 UpdateCameraY();
1138 truth game::HandleQuitMessage () {
1139 if (IsRunning()) {
1140 if (IsInGetCommand()) {
1141 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)) {
1142 case 0:
1143 Save();
1144 RemoveSaves(false);
1145 break;
1146 case 2:
1147 GetCurrentArea()->SendNewDrawRequest();
1148 DrawEverything();
1149 return false;
1150 default:
1151 festring Msg = CONST_S("cowardly quit the game");
1152 Player->AddScoreEntry(Msg, 0.75);
1153 End(Msg, true, false);
1154 break;
1156 } 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)) {
1157 RemoveSaves();
1158 } else {
1159 GetCurrentArea()->SendNewDrawRequest();
1160 DrawEverything();
1161 return false;
1164 return true;
1168 int game::GetDirectionForVector (v2 Vector) {
1169 for (int c = 0; c < DIRECTION_COMMAND_KEYS; ++c) if (Vector == GetMoveVector(c)) return c;
1170 return DIR_ERROR;
1174 cchar *game::GetVerbalPlayerAlignment () {
1175 sLong Sum = 0;
1176 for (int c = 1; c <= GODS; ++c) {
1177 if (GetGod(c)->GetRelation() > 0) Sum += GetGod(c)->GetRelation() * (5 - GetGod(c)->GetAlignment());
1179 if (Sum > 15000) return "extremely lawful";
1180 if (Sum > 10000) return "very lawful";
1181 if (Sum > 5000) return "lawful";
1182 if (Sum > 1000) return "mildly lawful";
1183 if (Sum > -1000) return "neutral";
1184 if (Sum > -5000) return "mildly chaotic";
1185 if (Sum > -10000) return "chaotic";
1186 if (Sum > -15000) return "very chaotic";
1187 return "extremely chaotic";
1191 void game::CreateGods () {
1192 God = new god*[GODS+1];
1193 God[0] = 0;
1194 for (int c = 1; c < protocontainer<god>::GetSize(); ++c) God[c] = protocontainer<god>::GetProto(c)->Spawn();
1198 void game::BusyAnimation () {
1199 BusyAnimation(DOUBLE_BUFFER, false);
1203 void game::BusyAnimation (bitmap *Buffer, truth ForceDraw) {
1204 static clock_t LastTime = 0;
1205 static int Frame = 0;
1206 static blitdata B1 = {
1208 { 0, 0 },
1209 { 0, 0 },
1210 { RES.X, RES.Y },
1211 { 0 },
1215 static blitdata B2 = {
1217 { 0, 0 },
1218 { (RES.X >> 1) - 100, (RES.Y << 1) / 3 - 100 },
1219 { 200, 200 },
1220 { 0 },
1224 if (ForceDraw || clock()-LastTime > CLOCKS_PER_SEC/25) {
1225 B2.Bitmap = Buffer;
1226 B2.Dest.X = (RES.X>>1)-100+EnterTextDisplacement.X;
1227 B2.Dest.Y = (RES.Y<<1)/3-100+EnterTextDisplacement.Y;
1228 if (EnterImage) {
1229 B1.Bitmap = Buffer;
1230 EnterImage->NormalMaskedBlit(B1);
1232 BusyAnimationCache[Frame]->NormalBlit(B2);
1233 if (Buffer == DOUBLE_BUFFER) graphics::BlitDBToScreen();
1234 if (++Frame == 32) Frame = 0;
1235 LastTime = clock();
1240 void game::CreateBusyAnimationCache () {
1241 bitmap Elpuri(TILE_V2, TRANSPARENT_COLOR);
1242 Elpuri.ActivateFastFlag();
1243 packcol16 Color = MakeRGB16(60, 60, 60);
1244 igraph::GetCharacterRawGraphic()->MaskedBlit(&Elpuri, v2(64, 0), ZERO_V2, TILE_V2, &Color);
1245 bitmap Circle(v2(200, 200), TRANSPARENT_COLOR);
1246 Circle.ActivateFastFlag();
1247 for (int x = 0; x < 4; ++x) Circle.DrawPolygon(100, 100, 95+x, 50, MakeRGB16(255-12*x, 0, 0));
1248 blitdata B1 = {
1250 { 0, 0 },
1251 { 92, 92 },
1252 { TILE_SIZE, TILE_SIZE },
1253 { 0 },
1254 TRANSPARENT_COLOR,
1257 blitdata B2 = {
1259 { 0, 0 },
1260 { 0, 0 },
1261 { 200, 200 },
1262 { 0 },
1263 TRANSPARENT_COLOR,
1266 for (int c = 0; c < 32; ++c) {
1267 B1.Bitmap = B2.Bitmap = BusyAnimationCache[c] = new bitmap(v2(200, 200), 0);
1268 B1.Bitmap->ActivateFastFlag();
1269 Elpuri.NormalMaskedBlit(B1);
1270 double Rotation = 0.3+c*FPI/80;
1271 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);
1272 Circle.NormalMaskedBlit(B2);
1277 int game::AskForKeyPress (cfestring &Topic) {
1278 DrawEverythingNoBlit();
1279 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Topic.CapitalizeCopy().CStr());
1280 graphics::BlitDBToScreen();
1281 int Key = GET_KEY();
1282 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1283 return Key;
1287 void game::AskForEscPress (cfestring &Topic) {
1288 DrawEverythingNoBlit();
1289 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), RED/*WHITE*/, "%s [press ESC]", Topic.CapitalizeCopy().CStr());
1290 graphics::BlitDBToScreen();
1291 int Key;
1292 do {
1293 Key = GET_KEY();
1294 } while (Key != KEY_ESC);
1295 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1299 /* Handler is called when the key has been identified as a movement key
1300 * KeyHandler is called when the key has NOT been identified as a movement key
1301 * Both can be deactivated by passing 0 as parameter */
1302 v2 game::PositionQuestion (cfestring &Topic, v2 CursorPos, void (*Handler)(v2), positionkeyhandler KeyHandler, truth Zoom) {
1303 int Key = 0;
1304 SetDoZoom(Zoom);
1305 v2 Return;
1306 CursorData = RED_CURSOR;
1307 if (Handler) Handler(CursorPos);
1308 for (;;) {
1309 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1310 if (!Square->HasBeenSeen() &&
1311 (!Square->GetCharacter() || !Square->GetCharacter()->CanBeSeenByPlayer()) &&
1312 !GetSeeWholeMapCheatMode()) DOUBLE_BUFFER->Fill(CalculateScreenCoordinates(CursorPos), TILE_V2, BLACK);
1313 else GetCurrentArea()->GetSquare(CursorPos)->SendStrongNewDrawRequest();
1315 if (Key == ' ' || Key == '.' || Key == KEY_NUMPAD_5) { Return = CursorPos; break; }
1316 if (Key == KEY_ESC) { Return = ERROR_V2; break; }
1318 v2 DirectionVector = GetDirectionVectorForKey(Key);
1319 if (DirectionVector != ERROR_V2) {
1320 CursorPos += DirectionVector;
1321 if (CursorPos.X > GetCurrentArea()->GetXSize()-1) CursorPos.X = 0;
1322 if (CursorPos.X < 0) CursorPos.X = GetCurrentArea()->GetXSize()-1;
1323 if (CursorPos.Y > GetCurrentArea()->GetYSize()-1) CursorPos.Y = 0;
1324 if (CursorPos.Y < 0) CursorPos.Y = GetCurrentArea()->GetYSize()-1;
1325 if (Handler) Handler(CursorPos);
1326 } else if (KeyHandler) {
1327 CursorPos = KeyHandler(CursorPos, Key);
1328 if (CursorPos == ERROR_V2 || CursorPos == ABORT_V2) {
1329 Return = CursorPos;
1330 break;
1334 if (ivanconfig::GetAutoCenterMapOnLook()) {
1335 UpdateCameraX(CursorPos.X);
1336 UpdateCameraY(CursorPos.Y);
1337 } else {
1338 if (CursorPos.X < GetCamera().X+3 || CursorPos.X >= GetCamera().X+GetScreenXSize()-3) UpdateCameraX(CursorPos.X);
1339 if (CursorPos.Y < GetCamera().Y+3 || CursorPos.Y >= GetCamera().Y+GetScreenYSize()-3) UpdateCameraY(CursorPos.Y);
1342 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Topic.CStr());
1343 SetCursorPos(CursorPos);
1344 DrawEverything();
1345 Key = GET_KEY();
1348 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1349 igraph::BlitBackGround(v2(RES.X-96, RES.Y-96), v2(80, 80));
1350 SetDoZoom(false);
1351 SetCursorPos(v2(-1, -1));
1352 return Return;
1356 void game::LookHandler (v2 CursorPos) {
1357 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1358 festring OldMemory;
1360 if (GetSeeWholeMapCheatMode()) {
1361 OldMemory = Square->GetMemorizedDescription();
1362 if (IsInWilderness()) GetWorldMap()->GetWSquare(CursorPos)->UpdateMemorizedDescription(true);
1363 else GetCurrentLevel()->GetLSquare(CursorPos)->UpdateMemorizedDescription(true);
1366 festring Msg;
1367 if (Square->HasBeenSeen() || GetSeeWholeMapCheatMode()) {
1368 if (!IsInWilderness() && !Square->CanBeSeenByPlayer() && GetCurrentLevel()->GetLSquare(CursorPos)->CanBeFeltByPlayer())
1369 Msg = CONST_S("You feel here ");
1370 else if (Square->CanBeSeenByPlayer(true) || GetSeeWholeMapCheatMode())
1371 Msg = CONST_S("You see here ");
1372 else
1373 Msg = CONST_S("You remember here ");
1374 Msg << Square->GetMemorizedDescription() << '.';
1375 if (!IsInWilderness() && (Square->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) {
1376 lsquare *LSquare = GetCurrentLevel()->GetLSquare(CursorPos);
1377 LSquare->DisplaySmokeInfo(Msg);
1378 if (LSquare->HasEngravings() && LSquare->IsTransparent()) {
1379 if (LSquare->EngravingsCanBeReadByPlayer() || GetSeeWholeMapCheatMode()) LSquare->DisplayEngravedInfo(Msg);
1380 else Msg << " Something has been engraved here.";
1383 } else Msg = CONST_S("You have never been here.");
1384 character *Character = Square->GetCharacter();
1385 if (Character && (Character->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) Character->DisplayInfo(Msg);
1386 if (!(RAND()%10000) && (Square->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) Msg << " You see here a frog eating a magnolia.";
1387 ADD_MESSAGE("%s", Msg.CStr());
1388 if (GetSeeWholeMapCheatMode()) Square->SetMemorizedDescription(OldMemory);
1392 truth game::AnimationController () {
1393 DrawEverythingNoBlit(true);
1394 return true;
1398 void game::LoadGlobalValueMap (inputfile &fl) {
1399 festring word;
1400 fl.setGetVarCB(game::ldrGetVar);
1401 for (fl.ReadWord(word, false); !fl.Eof(); fl.ReadWord(word, false)) {
1402 if (word == "Include") {
1403 word = fl.ReadWord();
1404 if (fl.ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1405 //fprintf(stderr, "loading: %s\n", word.CStr());
1406 inputfile incf(game::GetGameDir()+"Script/"+word, &game::GetGlobalValueMap());
1407 LoadGlobalValueMap(incf);
1408 continue;
1410 if (word == "Message") {
1411 word = fl.ReadWord();
1412 if (fl.ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1413 fprintf(stderr, "MESSAGE: %s\n", word.CStr());
1414 continue;
1416 if (word != "#") ABORT("Illegal datafile define in file %s on line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1417 fl.ReadWord(word, true);
1418 if (word == "enum" || word == "bitenum") {
1419 truth isBit = word == "bitenum";
1420 sLong idx = 0;
1421 if (fl.ReadWord() != "{") ABORT("'{' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1422 festring idName;
1423 truth done = false;
1424 while (!done) {
1425 fl.ReadWord(word, true);
1426 if (word == "}") break;
1427 if (word == "=") {
1428 idName.Empty();
1429 } else {
1430 idName = word;
1431 fl.ReadWord(word, true);
1433 if (word == "=") {
1434 // set current index
1435 idx = fl.ReadNumber();
1436 } else {
1437 if (word != "," && word != ";" && word != "}") ABORT("',' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1438 if (word == "}") done = true;
1440 if (idName.GetSize() > 0) {
1441 sLong i = idx;
1442 if (isBit) i = 1<<i;
1443 GlobalValueMap.insert(std::make_pair(idName, i));
1444 idx++;
1447 fl.SkipSpaces();
1448 int ch = fl.Get();
1449 if (ch != EOF && ch != ';') fl.Unget(ch);
1450 //if (fl.ReadWord() != ";") ABORT("';' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1451 continue;
1453 if (word == "define") {
1454 fl.ReadWord(word);
1455 sLong v = fl.ReadNumber();
1456 GlobalValueMap.insert(std::make_pair(word, v));
1457 continue;
1459 ABORT("Illegal datafile define in file %s on line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1464 void game::InitGlobalValueMap () {
1465 inputfile SaveFile(GetGameDir()+"Script/define.dat", &GlobalValueMap);
1466 LoadGlobalValueMap(SaveFile);
1467 { /* additional files */
1468 for (int f = 0; f <= 99; f++) {
1469 char bnum[32];
1470 sprintf(bnum, "Script/define_%02d.dat", f);
1471 festring fn = game::GetGameDir();
1472 fn << bnum;
1473 if (inputfile::fileExists(fn)) return;
1474 inputfile ifl(fn, &game::GetGlobalValueMap(), false);
1475 if (ifl.IsOpen()) {
1476 LoadGlobalValueMap(ifl);
1477 ifl.Close();
1484 void game::TextScreen (cfestring &Text, v2 Displacement, col16 Color, truth GKey, truth Fade, bitmapeditor BitmapEditor) {
1485 globalwindowhandler::DisableControlLoops();
1486 iosystem::TextScreen(Text, Displacement, Color, GKey, Fade, BitmapEditor);
1487 globalwindowhandler::EnableControlLoops();
1491 /* ... all the keys that are acceptable
1492 DefaultAnswer = REQUIRES_ANSWER if this question requires an answer
1493 Not surprisingly KeyNumber is the number of keys at ...
1495 int game::KeyQuestion (cfestring &Message, int DefaultAnswer, int KeyNumber, ...) {
1496 int *Key = new int[KeyNumber];
1497 va_list Arguments;
1498 va_start(Arguments, KeyNumber);
1499 for (int c = 0; c < KeyNumber; ++c) Key[c] = va_arg(Arguments, int);
1500 va_end(Arguments);
1501 DrawEverythingNoBlit();
1502 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Message.CStr());
1503 graphics::BlitDBToScreen();
1504 int Return = 0;
1505 while (!Return) {
1506 int k = GET_KEY();
1507 for (int c = 0; c < KeyNumber; ++c) {
1508 if (Key[c] == k) {
1509 Return = k;
1510 break;
1513 if (!Return && DefaultAnswer != REQUIRES_ANSWER) Return = DefaultAnswer;
1515 delete [] Key;
1516 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1517 return Return;
1521 v2 game::LookKeyHandler (v2 CursorPos, int Key) {
1522 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1523 switch (Key) {
1524 case 'i':
1525 if (!IsInWilderness()) {
1526 if (Square->CanBeSeenByPlayer() || CursorPos == Player->GetPos() || GetSeeWholeMapCheatMode()) {
1527 lsquare *LSquare = GetCurrentLevel()->GetLSquare(CursorPos);
1528 stack *Stack = LSquare->GetStack();
1529 if (LSquare->IsTransparent() && Stack->GetVisibleItems(Player))
1530 Stack->DrawContents(Player, "Items here", NO_SELECT|(GetSeeWholeMapCheatMode() ? 0 : NO_SPECIAL_INFO));
1531 else
1532 ADD_MESSAGE("You see no items here.");
1533 } else ADD_MESSAGE("You should perhaps move a bit closer.");
1535 break;
1536 case 'c':
1537 if (Square->CanBeSeenByPlayer() || CursorPos == Player->GetPos() || GetSeeWholeMapCheatMode()) {
1538 character *Char = Square->GetCharacter();
1539 if (Char && (Char->CanBeSeenByPlayer() || Char->IsPlayer() || GetSeeWholeMapCheatMode()))
1540 Char->PrintInfo();
1541 else
1542 ADD_MESSAGE("You see no one here.");
1543 } else ADD_MESSAGE("You should perhaps move a bit closer.");
1544 break;
1546 return CursorPos;
1550 v2 game::NameKeyHandler (v2 CursorPos, int Key) {
1551 if (SelectPet(Key)) return LastPetUnderCursor->GetPos();
1552 if (Key == 'n' || Key == 'N') {
1553 character *Char = GetCurrentArea()->GetSquare(CursorPos)->GetCharacter();
1554 if (Char && Char->CanBeSeenByPlayer()) Char->TryToName();
1555 else ADD_MESSAGE("You don't see anyone here to name.");
1557 return CursorPos;
1561 void game::End (festring DeathMessage, truth Permanently, truth AndGoToMenu) {
1562 pool::AbortBe();
1563 globalwindowhandler::DeInstallControlLoop(AnimationController);
1564 SetIsRunning(false);
1565 if (Permanently || !WizardModeIsReallyActive()) RemoveSaves(Permanently);
1566 if (Permanently && !WizardModeIsReallyActive()) {
1567 highscore HScore;
1569 if (HScore.LastAddFailed()) {
1570 iosystem::TextScreen(CONST_S("You didn't manage to get onto the high score list.\n\n\n\n")+GetPlayerName()+", "+DeathMessage+"\nRIP");
1571 } else {
1572 HScore.Draw();
1575 if (AndGoToMenu) {
1576 /* This prevents monster movement etc. after death. */
1577 throw quitrequest();
1582 int game::CalculateRoughDirection (v2 Vector) {
1583 if (!Vector.X && !Vector.Y) return YOURSELF;
1584 double Angle = femath::CalculateAngle(Vector);
1585 if (Angle < FPI / 8) return 4;
1586 else if (Angle < 3*FPI/8) return 7;
1587 else if (Angle < 5*FPI/8) return 6;
1588 else if (Angle < 7*FPI/8) return 5;
1589 else if (Angle < 9*FPI/8) return 3;
1590 else if (Angle < 11*FPI/8) return 0;
1591 else if (Angle < 13*FPI/8) return 1;
1592 else if (Angle < 15*FPI/8) return 2;
1593 else return 4;
1597 int game::Menu (bitmap *BackGround, v2 Pos, cfestring &Topic, cfestring &sMS, col16 Color, cfestring &SmallText1, cfestring &SmallText2) {
1598 globalwindowhandler::DisableControlLoops();
1599 int Return = iosystem::Menu(BackGround, Pos, Topic, sMS, Color, SmallText1, SmallText2);
1600 globalwindowhandler::EnableControlLoops();
1601 return Return;
1605 void game::InitDangerMap () {
1606 truth First = true;
1607 pool::RegisterState(false);
1608 //fprintf(stderr, "game::InitDangerMap(): START\n");
1609 for (int c1 = 1; c1 < protocontainer<character>::GetSize(); ++c1) {
1610 BusyAnimation();
1611 const character::prototype *Proto = protocontainer<character>::GetProto(c1);
1612 const character::database *const *ConfigData = Proto->GetConfigData();
1613 int ConfigSize = Proto->GetConfigSize();
1614 for (int c2 = 0; c2 < ConfigSize; ++c2) {
1615 if (!ConfigData[c2]->IsAbstract) {
1616 int Config = ConfigData[c2]->Config;
1618 if (First) {
1619 NextDangerIDType = c1;
1620 NextDangerIDConfigIndex = c2;
1621 First = false;
1623 character *Char = Proto->Spawn(Config, NO_EQUIPMENT|NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1624 double NakedDanger = Char->GetRelativeDanger(Player, true);
1625 //fprintf(stderr, " game::InitDangerMap(): 00\n");
1626 delete Char;
1627 //Char->SendToHell(); // k8: equipment
1628 Char = Proto->Spawn(Config, NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1629 double EquippedDanger = Char->GetRelativeDanger(Player, true);
1630 //fprintf(stderr, " game::InitDangerMap(): 01\n");
1631 delete Char;
1632 //Char->SendToHell(); // k8: equipment
1633 DangerMap[configid(c1, Config)] = dangerid(NakedDanger, EquippedDanger);
1637 pool::RegisterState(true);
1638 //fprintf(stderr, "game::InitDangerMap(): DONE\n");
1642 void game::CalculateNextDanger () {
1643 if (IsInWilderness() || !*CurrentLevel->GetLevelScript()->GenerateMonsters()) return;
1644 const character::prototype *Proto = protocontainer<character>::GetProto(NextDangerIDType);
1645 const character::database *const *ConfigData = Proto->GetConfigData();
1646 const character::database *DataBase = ConfigData[NextDangerIDConfigIndex];
1647 dangermap::iterator DangerIterator = DangerMap.find(configid(NextDangerIDType, DataBase->Config));
1648 team *Team = GetTeam(PLAYER_TEAM);
1649 if (DataBase && DangerIterator != DangerMap.end()) {
1650 //fprintf(stderr, "game::CalculateNextDanger(): START\n");
1651 pool::RegisterState(false);
1652 character *Char = Proto->Spawn(DataBase->Config, NO_EQUIPMENT|NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1653 std::list<character*>::const_iterator i;
1654 double DangerSum = Player->GetRelativeDanger(Char, true);
1655 for (i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i) {
1656 if ((*i)->IsEnabled() && !(*i)->IsTemporary() && !RAND_N(10)) DangerSum += (*i)->GetRelativeDanger(Char, true)/4;
1658 double CurrentDanger = 1/DangerSum;
1659 double NakedDanger = DangerIterator->second.NakedDanger;
1660 //fprintf(stderr, " game::CalculateNextDanger(): 00\n");
1661 delete Char;
1662 //Char->SendToHell(); // k8: equipment
1663 if (NakedDanger > CurrentDanger) DangerIterator->second.NakedDanger = (NakedDanger*9+CurrentDanger)/10;
1664 Char = Proto->Spawn(DataBase->Config, NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1665 DangerSum = Player->GetRelativeDanger(Char, true);
1666 for (i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i) {
1667 if ((*i)->IsEnabled() && !(*i)->IsTemporary() && !RAND_N(10)) DangerSum += (*i)->GetRelativeDanger(Char, true) / 4;
1669 CurrentDanger = 1/DangerSum;
1670 double EquippedDanger = DangerIterator->second.EquippedDanger;
1671 //fprintf(stderr, " game::CalculateNextDanger(): 01\n");
1672 delete Char;
1673 //Char->SendToHell(); // k8: equipment
1674 pool::RegisterState(true);
1675 if (EquippedDanger > CurrentDanger) DangerIterator->second.EquippedDanger = (EquippedDanger*9+CurrentDanger)/10;
1676 if (++NextDangerIDConfigIndex < Proto->GetConfigSize()) {
1677 //fprintf(stderr, "game::CalculateNextDanger(): EXIT0\n");
1678 return;
1680 for (;;) {
1681 if (++NextDangerIDType >= protocontainer<character>::GetSize()) NextDangerIDType = 1;
1682 Proto = protocontainer<character>::GetProto(NextDangerIDType);
1683 ConfigData = Proto->GetConfigData();
1684 int ConfigSize = Proto->GetConfigSize();
1686 for (int c = 0; c < ConfigSize; ++c) {
1687 if (!ConfigData[c]->IsAbstract) {
1688 NextDangerIDConfigIndex = c;
1689 //fprintf(stderr, "game::CalculateNextDanger(): EXIT1\n");
1690 return;
1694 //fprintf(stderr, "game::CalculateNextDanger(): DONE\n");
1695 } else {
1696 ABORT("It is dangerous to go ice fishing in the summer.");
1701 truth game::TryTravel (int Dungeon, int Area, int EntryIndex, truth AllowHostiles, truth AlliesFollow) {
1702 charactervector Group;
1703 if (LeaveArea(Group, AllowHostiles, AlliesFollow)) {
1704 CurrentDungeonIndex = Dungeon;
1705 EnterArea(Group, Area, EntryIndex);
1706 return true;
1708 return false;
1712 truth game::LeaveArea (charactervector &Group, truth AllowHostiles, truth AlliesFollow) {
1713 if (!IsInWilderness()) {
1714 if (AlliesFollow && !GetCurrentLevel()->CollectCreatures(Group, Player, AllowHostiles)) return false;
1715 Player->Remove();
1716 GetCurrentDungeon()->SaveLevel(SaveName(), CurrentLevelIndex);
1717 } else {
1718 Player->Remove();
1719 GetWorldMap()->GetPlayerGroup().swap(Group);
1720 SaveWorldMap();
1722 return true;
1726 /* Used always when the player enters an area. */
1727 void game::EnterArea (charactervector &Group, int Area, int EntryIndex) {
1728 if (Area != WORLD_MAP) {
1729 Generating = true;
1730 SetIsInWilderness(false);
1731 CurrentLevelIndex = Area;
1732 truth New = !PrepareRandomBone(Area) && !GetCurrentDungeon()->PrepareLevel(Area);
1733 igraph::CreateBackGround(*CurrentLevel->GetLevelScript()->GetBackGroundType());
1734 GetCurrentArea()->SendNewDrawRequest();
1735 v2 Pos = GetCurrentLevel()->GetEntryPos(Player, EntryIndex);
1736 if (Player) {
1737 GetCurrentLevel()->GetLSquare(Pos)->KickAnyoneStandingHereAway();
1738 Player->PutToOrNear(Pos);
1739 } else SetPlayer(GetCurrentLevel()->GetLSquare(Pos)->GetCharacter());
1740 uInt c;
1741 for (c = 0; c < Group.size(); ++c) {
1742 v2 NPCPos = GetCurrentLevel()->GetNearestFreeSquare(Group[c], Pos);
1743 if (NPCPos == ERROR_V2) NPCPos = GetCurrentLevel()->GetRandomSquare(Group[c]);
1744 Group[c]->PutTo(NPCPos);
1746 GetCurrentLevel()->FiatLux();
1747 ctruth *AutoReveal = GetCurrentLevel()->GetLevelScript()->AutoReveal();
1748 if (New && AutoReveal && *AutoReveal) GetCurrentLevel()->Reveal();
1749 ShowLevelMessage();
1750 SendLOSUpdateRequest();
1751 UpdateCamera();
1753 /* Gum solution! */
1754 if (New && CurrentDungeonIndex == ATTNAM && Area == 0) {
1755 GlobalRainLiquid = powder::Spawn(SNOW);
1756 GlobalRainSpeed = v2(-64, 128);
1757 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
1760 if (New && CurrentDungeonIndex == NEW_ATTNAM && Area == 0) {
1761 GlobalRainLiquid = liquid::Spawn(WATER);
1762 GlobalRainSpeed = v2(256, 512);
1763 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
1766 if (New && CurrentDungeonIndex == ELPURI_CAVE && Area == OREE_LAIR) {
1767 GlobalRainLiquid = liquid::Spawn(BLOOD);
1768 GlobalRainSpeed = v2(256, 512);
1769 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
1770 GlobalRainLiquid->SetVolumeNoSignals(200);
1771 CurrentLevel->EnableGlobalRain();
1774 if (New && CurrentDungeonIndex == MUNTUO && Area == 0) {
1775 GlobalRainLiquid = liquid::Spawn(WATER);
1776 GlobalRainSpeed = v2(-64, 1024);
1777 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
1780 Generating = false;
1781 GetCurrentLevel()->UpdateLOS();
1782 Player->SignalStepFrom(0);
1784 for (c = 0; c < Group.size(); ++c) Group[c]->SignalStepFrom(0);
1786 if (ivanconfig::GetAutoSaveInterval()) Save(GetAutoSaveFileName().CStr());
1787 } else {
1788 igraph::CreateBackGround(GRAY_FRACTAL);
1789 SetIsInWilderness(true);
1790 LoadWorldMap();
1791 SetCurrentArea(WorldMap);
1792 CurrentWSquareMap = WorldMap->GetMap();
1793 GetWorldMap()->GetPlayerGroup().swap(Group);
1794 Player->PutTo(GetWorldMap()->GetEntryPos(Player, EntryIndex));
1795 SendLOSUpdateRequest();
1796 UpdateCamera();
1797 GetWorldMap()->UpdateLOS();
1798 if (ivanconfig::GetAutoSaveInterval()) Save(GetAutoSaveFileName().CStr());
1803 int game::CompareLightToInt (col24 L, col24 Int) {
1804 if ((L & 0xFF0000) > Int || (L & 0xFF00) > Int || (L & 0xFF) > Int) return 1;
1805 if ((L & 0xFF0000) == Int || (L & 0xFF00) == Int || (L & 0xFF) == Int) return 0;
1806 return -1;
1810 void game::SetStandardListAttributes (felist &List) {
1811 List.SetPos(v2(26, 42));
1812 List.SetWidth(652);
1813 List.SetFlags(DRAW_BACKGROUND_AFTERWARDS);
1814 List.SetUpKey(GetMoveCommandKey(KEY_UP_INDEX));
1815 List.SetDownKey(GetMoveCommandKey(KEY_DOWN_INDEX));
1819 void game::InitPlayerAttributeAverage () {
1820 AveragePlayerArmStrengthExperience
1821 = AveragePlayerLegStrengthExperience
1822 = AveragePlayerDexterityExperience
1823 = AveragePlayerAgilityExperience
1824 = 0;
1826 if (!Player->IsHumanoid()) return;
1828 humanoid *Player = static_cast<humanoid*>(GetPlayer());
1829 int Arms = 0;
1830 int Legs = 0;
1831 arm *RightArm = Player->GetRightArm();
1833 if (RightArm && !RightArm->UseMaterialAttributes()) {
1834 AveragePlayerArmStrengthExperience += RightArm->GetStrengthExperience();
1835 AveragePlayerDexterityExperience += RightArm->GetDexterityExperience();
1836 ++Arms;
1839 arm *LeftArm = Player->GetLeftArm();
1841 if (LeftArm && !LeftArm->UseMaterialAttributes()) {
1842 AveragePlayerArmStrengthExperience += LeftArm->GetStrengthExperience();
1843 AveragePlayerDexterityExperience += LeftArm->GetDexterityExperience();
1844 ++Arms;
1847 leg *RightLeg = Player->GetRightLeg();
1849 if (RightLeg && !RightLeg->UseMaterialAttributes()) {
1850 AveragePlayerLegStrengthExperience += RightLeg->GetStrengthExperience();
1851 AveragePlayerAgilityExperience += RightLeg->GetAgilityExperience();
1852 ++Legs;
1855 leg *LeftLeg = Player->GetLeftLeg();
1857 if (LeftLeg && !LeftLeg->UseMaterialAttributes()) {
1858 AveragePlayerLegStrengthExperience += LeftLeg->GetStrengthExperience();
1859 AveragePlayerAgilityExperience += LeftLeg->GetAgilityExperience();
1860 ++Legs;
1863 if (Arms) {
1864 AveragePlayerArmStrengthExperience /= Arms;
1865 AveragePlayerDexterityExperience /= Arms;
1868 if (Legs) {
1869 AveragePlayerLegStrengthExperience /= Legs;
1870 AveragePlayerAgilityExperience /= Legs;
1875 void game::UpdatePlayerAttributeAverage () {
1876 if (!Player->IsHumanoid()) return;
1878 humanoid *Player = static_cast<humanoid*>(GetPlayer());
1879 double PlayerArmStrengthExperience = 0;
1880 double PlayerLegStrengthExperience = 0;
1881 double PlayerDexterityExperience = 0;
1882 double PlayerAgilityExperience = 0;
1883 int Arms = 0;
1884 int Legs = 0;
1885 arm *RightArm = Player->GetRightArm();
1887 if (RightArm && !RightArm->UseMaterialAttributes()) {
1888 PlayerArmStrengthExperience += RightArm->GetStrengthExperience();
1889 PlayerDexterityExperience += RightArm->GetDexterityExperience();
1890 ++Arms;
1893 arm *LeftArm = Player->GetLeftArm();
1895 if (LeftArm && !LeftArm->UseMaterialAttributes()) {
1896 PlayerArmStrengthExperience += LeftArm->GetStrengthExperience();
1897 PlayerDexterityExperience += LeftArm->GetDexterityExperience();
1898 ++Arms;
1901 leg *RightLeg = Player->GetRightLeg();
1903 if (RightLeg && !RightLeg->UseMaterialAttributes()) {
1904 PlayerLegStrengthExperience += RightLeg->GetStrengthExperience();
1905 PlayerAgilityExperience += RightLeg->GetAgilityExperience();
1906 ++Legs;
1909 leg *LeftLeg = Player->GetLeftLeg();
1911 if (LeftLeg && !LeftLeg->UseMaterialAttributes()) {
1912 PlayerLegStrengthExperience += LeftLeg->GetStrengthExperience();
1913 PlayerAgilityExperience += LeftLeg->GetAgilityExperience();
1914 ++Legs;
1917 if (Arms) {
1918 AveragePlayerArmStrengthExperience = (49 * AveragePlayerArmStrengthExperience + PlayerArmStrengthExperience / Arms) / 50;
1919 AveragePlayerDexterityExperience = (49 * AveragePlayerDexterityExperience + PlayerDexterityExperience / Arms) / 50;
1922 if (Legs) {
1923 AveragePlayerLegStrengthExperience = (49 * AveragePlayerLegStrengthExperience + PlayerLegStrengthExperience / Legs) / 50;
1924 AveragePlayerAgilityExperience = (49 * AveragePlayerAgilityExperience + PlayerAgilityExperience / Legs) / 50;
1929 void game::CallForAttention (v2 Pos, int RangeSquare) {
1930 for (int c = 0; c < GetTeams(); ++c) {
1931 if (GetTeam(c)->HasEnemy())
1932 for (std::list<character*>::const_iterator i = GetTeam(c)->GetMember().begin(); i != GetTeam(c)->GetMember().end(); ++i)
1933 if ((*i)->IsEnabled()) {
1934 sLong ThisDistance = HypotSquare(sLong((*i)->GetPos().X) - Pos.X, sLong((*i)->GetPos().Y) - Pos.Y);
1935 if (ThisDistance <= RangeSquare && !(*i)->IsGoingSomeWhere()) (*i)->SetGoingTo(Pos);
1941 outputfile &operator << (outputfile &SaveFile, const homedata *HomeData) {
1942 if (HomeData) {
1943 SaveFile.Put(1);
1944 SaveFile << HomeData->Pos << HomeData->Dungeon << HomeData->Level << HomeData->Room;
1945 } else SaveFile.Put(0);
1946 return SaveFile;
1950 inputfile &operator >> (inputfile &SaveFile, homedata *&HomeData) {
1951 if (SaveFile.Get()) {
1952 HomeData = new homedata;
1953 SaveFile >> HomeData->Pos >> HomeData->Dungeon >> HomeData->Level >> HomeData->Room;
1955 return SaveFile;
1959 feuLong game::CreateNewCharacterID (character *NewChar) {
1960 feuLong ID = NextCharacterID++;
1961 /*k8:??? if(CharacterIDMap.find(ID) != CharacterIDMap.end())
1962 int esko = esko = 2;*/
1963 CharacterIDMap.insert(std::make_pair(ID, NewChar));
1964 return ID;
1968 feuLong game::CreateNewItemID (item *NewItem) {
1969 feuLong ID = NextItemID++;
1970 /*k8:??? if(ItemIDMap.find(ID) != ItemIDMap.end())
1971 int esko = esko = 2;*/
1972 if (NewItem) ItemIDMap.insert(std::make_pair(ID, NewItem));
1973 return ID;
1977 feuLong game::CreateNewTrapID (entity *NewTrap) {
1978 feuLong ID = NextTrapID++;
1979 /*k8:??? if(TrapIDMap.find(ID) != TrapIDMap.end())
1980 int esko = esko = 2;*/
1981 if (NewTrap) TrapIDMap.insert(std::make_pair(ID, NewTrap));
1982 return ID;
1986 character *game::SearchCharacter (feuLong ID) {
1987 characteridmap::iterator Iterator = CharacterIDMap.find(ID);
1988 return Iterator != CharacterIDMap.end() ? Iterator->second : 0;
1992 item *game::SearchItem (feuLong ID) {
1993 itemidmap::iterator Iterator = ItemIDMap.find(ID);
1994 return Iterator != ItemIDMap.end() ? Iterator->second : 0;
1998 entity *game::SearchTrap (feuLong ID) {
1999 trapidmap::iterator Iterator = TrapIDMap.find(ID);
2000 return Iterator != TrapIDMap.end() ? Iterator->second : 0;
2004 outputfile &operator << (outputfile &SaveFile, const configid &Value) {
2005 SaveFile.Write(reinterpret_cast<cchar*>(&Value), sizeof(Value));
2006 return SaveFile;
2010 inputfile &operator >> (inputfile &SaveFile, configid &Value) {
2011 SaveFile.Read(reinterpret_cast<char*>(&Value), sizeof(Value));
2012 return SaveFile;
2016 outputfile &operator << (outputfile &SaveFile, const dangerid &Value) {
2017 SaveFile << Value.NakedDanger << Value.EquippedDanger;
2018 return SaveFile;
2022 inputfile &operator >> (inputfile &SaveFile, dangerid &Value) {
2023 SaveFile >> Value.NakedDanger >> Value.EquippedDanger;
2024 return SaveFile;
2028 /* The program can only create directories to the deepness of one, no more... */
2029 festring game::GetHomeDir () {
2030 festring Dir;
2031 Dir << getenv("HOME") << '/';
2032 return Dir;
2036 festring game::GetSaveDir () {
2037 festring Dir;
2038 #ifdef LOCAL_SAVES
2039 Dir << ivanconfig::GetMyDir() << "/Save/";
2040 #else
2041 Dir << getenv("HOME") << "/IvanSave/";
2042 #endif
2043 return Dir;
2047 festring game::GetGameDir () {
2048 /*k8! return DATADIR "/ivan/"; */
2049 /*k8! return DATADIR "/"; */
2050 festring Dir;
2051 Dir << ivanconfig::GetMyDir() << "/";
2052 return Dir;
2056 festring game::GetBoneDir () {
2057 /*k8! return LOCAL_STATE_DIR "/Bones/";*/
2058 festring Dir;
2059 #ifdef LOCAL_SAVES
2060 Dir << ivanconfig::GetMyDir() << "/Save/Bones/";
2061 #else
2062 Dir << getenv("HOME") << "/IvanSave/Bones/";
2063 #endif
2064 return Dir;
2068 level *game::GetLevel (int I) {
2069 return GetCurrentDungeon()->GetLevel(I);
2073 int game::GetLevels () {
2074 return GetCurrentDungeon()->GetLevels();
2078 void game::SignalDeath (ccharacter *Ghost, ccharacter *Murderer, festring DeathMsg) {
2079 if (InWilderness) DeathMsg << " in the world map";
2080 else DeathMsg << " in " << GetCurrentDungeon()->GetLevelDescription(CurrentLevelIndex);
2081 massacremap *MassacreMap;
2082 if (!Murderer) {
2083 ++MiscMassacreAmount;
2084 MassacreMap = &MiscMassacreMap;
2085 } else if(Murderer->IsPlayer()) {
2086 ++PlayerMassacreAmount;
2087 MassacreMap = &PlayerMassacreMap;
2088 } else if(Murderer->IsPet()) {
2089 ++PetMassacreAmount;
2090 MassacreMap = &PetMassacreMap;
2091 } else {
2092 ++MiscMassacreAmount;
2093 MassacreMap = &MiscMassacreMap;
2096 massacreid MI(Ghost->GetType(), Ghost->GetConfig(), Ghost->GetAssignedName());
2097 massacremap::iterator i = MassacreMap->find(MI);
2099 if (i == MassacreMap->end()) {
2100 i = MassacreMap->insert(std::make_pair(MI, killdata(1, Ghost->GetGenerationDanger()))).first;
2101 i->second.Reason.push_back(killreason(DeathMsg, 1));
2102 } else {
2103 ++i->second.Amount;
2104 i->second.DangerSum += Ghost->GetGenerationDanger();
2105 std::vector<killreason>& Reason = i->second.Reason;
2106 uInt c;
2107 for (c = 0; c < Reason.size(); ++c) {
2108 if (Reason[c].String == DeathMsg) {
2109 ++Reason[c].Amount;
2110 break;
2113 if (c == Reason.size()) Reason.push_back(killreason(DeathMsg, 1));
2118 void game::DisplayMassacreLists () {
2119 DisplayMassacreList(PlayerMassacreMap, "directly by you.", PlayerMassacreAmount);
2120 DisplayMassacreList(PetMassacreMap, "by your allies.", PetMassacreAmount);
2121 DisplayMassacreList(MiscMassacreMap, "by some other reason.", MiscMassacreAmount);
2125 struct massacresetentry {
2126 bool operator < (const massacresetentry &MSE) const { return festring::IgnoreCaseCompare(Key, MSE.Key); }
2127 festring Key;
2128 festring String;
2129 std::vector<festring> Details;
2130 int ImageKey;
2134 void game::DisplayMassacreList (const massacremap &MassacreMap, cchar *Reason, sLong Amount) {
2135 std::set<massacresetentry> MassacreSet;
2136 festring FirstPronoun;
2137 truth First = true;
2138 charactervector GraveYard;
2140 for (massacremap::const_iterator i1 = MassacreMap.begin(); i1 != MassacreMap.end(); ++i1) {
2141 character *Victim = protocontainer<character>::GetProto(i1->first.Type)->Spawn(i1->first.Config);
2142 Victim->SetAssignedName(i1->first.Name);
2143 massacresetentry Entry;
2144 GraveYard.push_back(Victim);
2145 Entry.ImageKey = AddToCharacterDrawVector(Victim);
2146 if (i1->second.Amount == 1) {
2147 Victim->AddName(Entry.Key, UNARTICLED);
2148 Victim->AddName(Entry.String, INDEFINITE);
2149 } else {
2150 Victim->AddName(Entry.Key, PLURAL);
2151 Entry.String << i1->second.Amount << ' ' << Entry.Key;
2153 if (First) {
2154 FirstPronoun = Victim->GetSex() == UNDEFINED ? "it" : Victim->GetSex() == MALE ? "he" : "she";
2155 First = false;
2157 const std::vector<killreason>& Reason = i1->second.Reason;
2158 std::vector<festring>& Details = Entry.Details;
2159 if (Reason.size() == 1) {
2160 festring Begin;
2161 if (Reason[0].Amount == 1) Begin = "";
2162 else if(Reason[0].Amount == 2) Begin = "both ";
2163 else Begin = "all ";
2164 Details.push_back(Begin + Reason[0].String);
2165 } else {
2166 for (uInt c = 0; c < Reason.size(); ++c) Details.push_back(CONST_S("")+Reason[c].Amount+' '+Reason[c].String);
2167 std::sort(Details.begin(), Details.end(), ignorecaseorderer());
2169 MassacreSet.insert(Entry);
2171 sLong Total = PlayerMassacreAmount+PetMassacreAmount+MiscMassacreAmount;
2172 festring MainTopic;
2173 if (Total == 1) MainTopic << "One creature perished during your adventure.";
2174 else MainTopic << Total << " creatures perished during your adventure.";
2175 felist List(MainTopic);
2176 SetStandardListAttributes(List);
2177 List.SetPageLength(15);
2178 List.AddFlags(SELECTABLE);
2179 List.SetEntryDrawer(CharacterEntryDrawer);
2180 List.AddDescription(CONST_S(""));
2181 festring SideTopic;
2182 if (Amount != Total) {
2183 SideTopic = CONST_S("The following ");
2184 if (Amount == 1) SideTopic << "one was killed " << Reason;
2185 else SideTopic << Amount << " were killed " << Reason;
2186 } else {
2187 if (Amount == 1) {
2188 FirstPronoun.Capitalize();
2189 SideTopic << FirstPronoun << " was killed " << Reason;
2190 } else SideTopic << "They were all killed " << Reason;
2192 List.AddDescription(SideTopic);
2193 List.AddDescription(CONST_S(""));
2194 List.AddDescription("Choose a type of creatures to browse death details.");
2195 std::set<massacresetentry>::const_iterator i2;
2196 for (i2 = MassacreSet.begin(); i2 != MassacreSet.end(); ++i2) List.AddEntry(i2->String, LIGHT_GRAY, 0, i2->ImageKey);
2197 for (;;) {
2198 int Chosen = List.Draw();
2199 if (Chosen & FELIST_ERROR_BIT) break;
2200 felist SubList(CONST_S("Massacre details"));
2201 SetStandardListAttributes(SubList);
2202 SubList.SetPageLength(20);
2203 int Counter = 0;
2204 for (i2 = MassacreSet.begin(); i2 != MassacreSet.end(); ++i2, ++Counter) {
2205 if (Counter == Chosen) {
2206 for (uInt c = 0; c < i2->Details.size(); ++c) SubList.AddEntry(i2->Details[c], LIGHT_GRAY);
2207 break;
2210 SubList.Draw();
2212 ClearCharacterDrawVector();
2213 for (uInt c = 0; c < GraveYard.size(); ++c) delete GraveYard[c];
2217 truth game::MassacreListsEmpty () {
2218 return PlayerMassacreMap.empty() && PetMassacreMap.empty() && MiscMassacreMap.empty();
2222 #ifdef WIZARD
2223 void game::SeeWholeMap () {
2224 if (SeeWholeMapCheatMode < 2) ++SeeWholeMapCheatMode; else SeeWholeMapCheatMode = 0;
2225 GetCurrentArea()->SendNewDrawRequest();
2227 #endif
2230 void game::CreateBone () {
2231 if (!WizardModeIsActive() && !IsInWilderness() && RAND() & 3 && GetCurrentLevel()->PreProcessForBone()) {
2232 int BoneIndex;
2233 festring BoneName;
2234 for (BoneIndex = 0; BoneIndex < 1000; ++BoneIndex) {
2235 BoneName = GetBoneDir()+"bon"+CurrentDungeonIndex+CurrentLevelIndex+BoneIndex;
2236 if (!inputfile::fileExists(BoneName)) break;
2238 if (BoneIndex != 1000) {
2239 //festring BoneName = GetBoneDir()+"bon"+CurrentDungeonIndex+CurrentLevelIndex+BoneIndex;
2240 fprintf(stderr, "creating bone file: [%s]\n", BoneName.CStr());
2241 outputfile BoneFile(BoneName, true);
2242 BoneFile << int(BONE_FILE_VERSION) << PlayerName << CurrentLevel;
2248 truth game::PrepareRandomBone (int LevelIndex) {
2249 if (/*k8:WizardModeIsActive() ||*/ GetCurrentDungeon()->IsGenerated(LevelIndex) || !*GetCurrentDungeon()->GetLevelScript(LevelIndex)->CanGenerateBone()) return false;
2250 int BoneIndex;
2251 festring BoneName;
2252 for (BoneIndex = 0; BoneIndex < 1000; ++BoneIndex) {
2253 BoneName = GetBoneDir()+"bon"+CurrentDungeonIndex+LevelIndex+BoneIndex;
2254 inputfile BoneFile(BoneName, 0, false);
2255 if (BoneFile.IsOpen() && !(RAND() & 7)) {
2256 if (ReadType<int>(BoneFile) != BONE_FILE_VERSION) {
2257 BoneFile.Close();
2258 remove(BoneName.CStr());
2259 continue;
2261 festring Name;
2262 BoneFile >> Name;
2263 level *NewLevel = GetCurrentDungeon()->LoadLevel(BoneFile, LevelIndex);
2264 if (!NewLevel->PostProcessForBone()) {
2265 delete NewLevel;
2266 GetBoneItemIDMap().clear();
2267 GetBoneCharacterIDMap().clear();
2268 continue;
2270 NewLevel->FinalProcessForBone();
2271 GetBoneItemIDMap().clear();
2272 GetBoneCharacterIDMap().clear();
2273 SetCurrentArea(NewLevel);
2274 CurrentLevel = NewLevel;
2275 CurrentLSquareMap = NewLevel->GetMap();
2276 GetCurrentDungeon()->SetIsGenerated(LevelIndex, true);
2277 if (Name == PlayerName) ADD_MESSAGE("This place is oddly familiar. Like you had been here in one of your past lives.");
2278 else ADD_MESSAGE("You smell the stench of death.");
2279 break;
2282 Generating = true;
2283 if (BoneIndex != 1000) {
2284 remove(BoneName.CStr());
2285 return true;
2287 return false;
2291 double game::CalculateAverageDanger (const charactervector &EnemyVector, character *Char) {
2292 double DangerSum = 0;
2293 int Enemies = 0;
2294 for (uInt c = 0; c < EnemyVector.size(); ++c) {
2295 DangerSum += EnemyVector[c]->GetRelativeDanger(Char, true);
2296 ++Enemies;
2298 return DangerSum/Enemies;
2302 double game::CalculateAverageDangerOfAllNormalEnemies () {
2303 double DangerSum = 0;
2304 int Enemies = 0;
2305 for (int c1 = 1; c1 < protocontainer<character>::GetSize(); ++c1) {
2306 const character::prototype *Proto = protocontainer<character>::GetProto(c1);
2307 const character::database*const *ConfigData = Proto->GetConfigData();
2308 int ConfigSize = Proto->GetConfigSize();
2309 for (int c2 = 0; c2 < ConfigSize; ++c2) {
2310 if (!ConfigData[c2]->IsAbstract && !ConfigData[c2]->IsUnique && ConfigData[c2]->CanBeGenerated) {
2311 DangerSum += DangerMap.find(configid(c1, ConfigData[c2]->Config))->second.EquippedDanger;
2312 ++Enemies;
2316 return DangerSum/Enemies;
2320 character *game::CreateGhost () {
2321 double AverageDanger = CalculateAverageDangerOfAllNormalEnemies();
2322 charactervector EnemyVector;
2323 protosystem::CreateEveryNormalEnemy(EnemyVector);
2324 ghost *Ghost = ghost::Spawn();
2325 Ghost->SetTeam(GetTeam(MONSTER_TEAM));
2326 Ghost->SetGenerationDanger(CurrentLevel->GetDifficulty());
2327 Ghost->SetOwnerSoul(PlayerName);
2328 Ghost->SetIsActive(false);
2329 Ghost->EditAllAttributes(-4);
2330 Player->SetSoulID(Ghost->GetID());
2331 while (CalculateAverageDanger(EnemyVector, Ghost) > AverageDanger && Ghost->EditAllAttributes(1));
2332 for (uInt c = 0; c < EnemyVector.size(); ++c) delete EnemyVector[c];
2333 return Ghost;
2337 int game::GetMoveCommandKey (int I) {
2338 if (!ivanconfig::GetUseAlternativeKeys()) return MoveNormalCommandKey[I];
2339 return MoveAbnormalCommandKey[I];
2343 sLong game::GetScore () {
2344 double Counter = 0;
2345 massacremap::const_iterator i;
2346 massacremap SumMap = PlayerMassacreMap;
2347 for (i = PetMassacreMap.begin(); i != PetMassacreMap.end(); ++i) {
2348 killdata &KillData = SumMap[i->first];
2349 KillData.Amount += i->second.Amount;
2350 KillData.DangerSum += i->second.DangerSum;
2352 for (i = SumMap.begin(); i != SumMap.end(); ++i) {
2353 character *Char = protocontainer<character>::GetProto(i->first.Type)->Spawn(i->first.Config);
2354 int SumOfAttributes = Char->GetSumOfAttributes();
2355 Counter += sqrt(i->second.DangerSum / DEFAULT_GENERATION_DANGER) * SumOfAttributes * SumOfAttributes;
2356 delete Char;
2358 return sLong(0.01*Counter);
2362 /* Only works if New Attnam is loaded */
2363 truth game::TweraifIsFree () {
2364 for (std::list<character*>::const_iterator i = GetTeam(COLONIST_TEAM)->GetMember().begin(); i != GetTeam(COLONIST_TEAM)->GetMember().end(); ++i)
2365 if ((*i)->IsEnabled()) return false;
2366 return true;
2370 // returns true if date is christmaseve or day
2371 truth game::IsXMas () {
2372 time_t Time = time(0);
2373 struct tm *TM = localtime(&Time);
2374 return (TM->tm_mon == 11 && (TM->tm_mday == 24 || TM->tm_mday == 25));
2378 int game::AddToItemDrawVector (const itemvector &What) {
2379 ItemDrawVector.push_back(What);
2380 return ItemDrawVector.size()-1;
2384 v2 ItemDisplacement[3][3] = {
2385 { v2(0, 0), ERROR_V2, ERROR_V2 },
2386 { v2(-2, -2), v2(2, 2), ERROR_V2 },
2387 { v2(-4, -4), v2(0, 0), v2(4, 4) }
2391 void game::ItemEntryDrawer (bitmap *Bitmap, v2 Pos, uInt I) {
2392 blitdata B = {
2393 Bitmap,
2394 { 0, 0 },
2395 { 0, 0 },
2396 { TILE_SIZE, TILE_SIZE },
2397 { NORMAL_LUMINANCE },
2398 TRANSPARENT_COLOR,
2399 ALLOW_ANIMATE
2401 itemvector ItemVector = ItemDrawVector[I];
2402 int Amount = Min<int>(ItemVector.size(), 3);
2403 for (int c = 0; c < Amount; ++c) {
2404 v2 Displacement = ItemDisplacement[Amount-1][c];
2405 if (!ItemVector[0]->HasNormalPictureDirection()) Displacement.X = -Displacement.X;
2406 B.Dest = Pos+Displacement;
2407 if (ItemVector[c]->AllowAlphaEverywhere()) B.CustomData |= ALLOW_ALPHA;
2408 ItemVector[c]->Draw(B);
2409 B.CustomData &= ~ALLOW_ALPHA;
2411 if (ItemVector.size() > 3) {
2412 B.Src.X = 0;
2413 B.Src.Y = 16;
2414 B.Dest = ItemVector[0]->HasNormalPictureDirection() ? Pos+v2(11, -2) : Pos+v2(-2, -2);
2415 B.Flags = 0;
2416 igraph::GetSymbolGraphic()->NormalMaskedBlit(B);
2421 int game::AddToCharacterDrawVector (character *What) {
2422 CharacterDrawVector.push_back(What);
2423 return CharacterDrawVector.size()-1;
2427 void game::CharacterEntryDrawer (bitmap *Bitmap, v2 Pos, uInt I) {
2428 if (CharacterDrawVector[I]) {
2429 blitdata B = {
2430 Bitmap,
2431 { 0, 0 },
2432 { Pos.X, Pos.Y },
2433 { TILE_SIZE, TILE_SIZE },
2434 { NORMAL_LUMINANCE },
2435 TRANSPARENT_COLOR,
2436 ALLOW_ANIMATE|ALLOW_ALPHA
2438 CharacterDrawVector[I]->DrawBodyParts(B);
2443 void game::GodEntryDrawer (bitmap *Bitmap, v2 Pos, uInt I) {
2444 blitdata B = {
2445 Bitmap,
2446 { I << 4, 0 },
2447 { Pos.X, Pos.Y },
2448 { TILE_SIZE, TILE_SIZE },
2449 { 0 },
2450 TRANSPARENT_COLOR,
2453 igraph::GetSymbolGraphic()->NormalMaskedBlit(B);
2457 character *game::GetSumo () {
2458 return GetCurrentLevel()->GetLSquare(SUMO_ROOM_POS)->GetRoom()->GetMaster();
2462 truth game::TryToEnterSumoArena () {
2463 character *Sumo = GetSumo();
2464 if (!Sumo || !Sumo->IsEnabled() || Sumo->GetRelation(Player) == HOSTILE || !Player->CanBeSeenBy(Sumo)) return true;
2465 if (TweraifIsFree()) {
2466 ADD_MESSAGE("\"You started this stupid revolution, after which I've been constantly hungry. Get lost!\"");
2467 return false;
2469 if (PlayerIsSumoChampion()) {
2470 ADD_MESSAGE("\"I don't really enjoy losing, especially many times to the same guy. Go away.\"");
2471 return false;
2473 if (Player->IsPolymorphed()) {
2474 ADD_MESSAGE("\"Don't try to cheat. Come back when you're normal again.\"");
2475 return false;
2477 if (Player->GetHungerState() < SATIATED) {
2478 ADD_MESSAGE("\"Your figure is too slender for this sport. Eat a lot more and come back.\"");
2479 return false;
2481 if (Player->GetHungerState() < BLOATED) {
2482 ADD_MESSAGE("\"You're still somewhat too thin. Eat some more and we'll compete.\"");
2483 return false;
2485 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.\"");
2486 if (!TruthQuestion("Do you want to challenge him? [y/N]")) return false;
2487 pool::AbortBe();
2488 SumoWrestling = true;
2489 character *MirrorPlayer = Player->Duplicate(IGNORE_PROHIBITIONS);
2490 character *MirrorSumo = Sumo->Duplicate(IGNORE_PROHIBITIONS);
2491 SetPlayer(MirrorPlayer);
2492 charactervector Spectators;
2493 if (Player->GetTeam()->GetRelation(GetTeam(TOURIST_GUIDE_TEAM)) != HOSTILE &&
2494 Player->GetTeam()->GetRelation(GetTeam(TOURIST_TEAM)) != HOSTILE) {
2495 GetTeam(TOURIST_GUIDE_TEAM)->MoveMembersTo(Spectators);
2496 GetTeam(TOURIST_TEAM)->MoveMembersTo(Spectators);
2498 GetCurrentDungeon()->SaveLevel(SaveName(), 0);
2499 charactervector test;
2500 EnterArea(test, 1, STAIRS_UP);
2501 MirrorSumo->PutTo(SUMO_ARENA_POS+v2(6, 5));
2502 MirrorSumo->ChangeTeam(GetTeam(SUMO_TEAM));
2503 GetCurrentLevel()->GetLSquare(SUMO_ARENA_POS)->GetRoom()->SetMasterID(MirrorSumo->GetID());
2504 for (uInt c = 0; c < Spectators.size(); ++c) Spectators[c]->PutToOrNear(SUMO_ARENA_POS + v2(6, 10));
2505 throw areachangerequest();
2506 return true;
2510 truth game::TryToExitSumoArena () {
2511 if (GetTeam(PLAYER_TEAM)->GetRelation(GetTeam(NEW_ATTNAM_TEAM)) == HOSTILE) return true;
2512 itemvector IVector;
2513 charactervector CVector;
2514 if (IsSumoWrestling()) {
2515 if (TruthQuestion("Do you really wish to give up? [y/N]")) return EndSumoWrestling(LOST);
2516 return false;
2517 } else {
2518 pool::AbortBe();
2519 Player->Remove();
2520 GetCurrentLevel()->CollectEverything(IVector, CVector);
2521 GetCurrentDungeon()->SaveLevel(SaveName(), 1);
2522 std::vector<character*> test;
2523 EnterArea(test, 0, STAIRS_DOWN);
2524 Player->GetStackUnder()->AddItems(IVector);
2525 if (!IVector.empty()) {
2526 character *Sumo = GetSumo();
2527 if (Sumo && Sumo->GetRelation(Player) != HOSTILE && Player->CanBeSeenBy(Sumo)) ADD_MESSAGE("\"Don't leave anything there, please.\"");
2529 v2 PlayerPos = Player->GetPos();
2530 for (uInt c = 0; c < CVector.size(); ++c) CVector[c]->PutNear(PlayerPos);
2531 throw areachangerequest();
2532 return true;
2537 truth game::EndSumoWrestling (int Result) {
2538 pool::AbortBe();
2539 msgsystem::LeaveBigMessageMode();
2540 if (Result == LOST) AskForKeyPress("You lose. [press any key to continue]");
2541 else if (Result == WON) AskForKeyPress("You win! [press any key to continue]");
2542 else if (Result == DISQUALIFIED) AskForKeyPress("You are disqualified! [press any key to continue]");
2543 character *Sumo = GetCurrentLevel()->GetLSquare(SUMO_ARENA_POS)->GetRoom()->GetMaster();
2544 /* We'll make a throw soon so deletes are allowed */
2545 if (Sumo) {
2546 Sumo->Remove();
2547 delete Sumo;
2549 Player->Remove();
2550 delete Player;
2551 SetPlayer(0);
2552 itemvector IVector;
2553 charactervector CVector;
2554 GetCurrentLevel()->CollectEverything(IVector, CVector);
2555 GetCurrentDungeon()->SaveLevel(SaveName(), 1);
2556 charactervector test;
2557 EnterArea(test, 0, STAIRS_DOWN);
2558 SumoWrestling = false;
2559 Player->GetStackUnder()->AddItems(IVector);
2560 v2 PlayerPos = Player->GetPos();
2561 for (uInt c = 0; c < CVector.size(); ++c) CVector[c]->PutNear(PlayerPos);
2562 if (Result == LOST) ADD_MESSAGE("\"I hope you've learned your lesson now!\"");
2563 else if (Result == DISQUALIFIED) ADD_MESSAGE("\"Don't do that again or I'll be really angry!\"");
2564 else {
2565 PlayerSumoChampion = true;
2566 character *Sumo = GetSumo();
2567 festring Msg = Sumo->GetName(DEFINITE)+" seems humbler than before. \"Darn. You bested me.\n";
2568 Msg << "Here's a little something as a reward\", " << Sumo->GetPersonalPronoun() << " says and hands you a belt of levitation.\n\"";
2569 (belt::Spawn(BELT_OF_LEVITATION))->MoveTo(Player->GetStack());
2570 Msg << "Allow me to also teach you a few nasty martial art tricks the years have taught me.\"";
2571 Player->GetCWeaponSkill(UNARMED)->AddHit(100000);
2572 Player->GetCWeaponSkill(KICK)->AddHit(100000);
2573 character *Imperialist = GetCurrentLevel()->GetLSquare(5, 5)->GetRoom()->GetMaster();
2574 if (Imperialist && Imperialist->GetRelation(Player) != HOSTILE) {
2575 v2 Pos = Player->GetPos()+v2(0, 1);
2576 GetCurrentLevel()->GetLSquare(Pos)->KickAnyoneStandingHereAway();
2577 Imperialist->Remove();
2578 Imperialist->PutTo(Pos);
2579 Msg << "\n\nSuddenly you notice " << Imperialist->GetName(DEFINITE) << " has also entered.\n"
2580 "\"I see we have a promising fighter among us. I had already heard of your\n"
2581 "adventures outside the village, but hardly could I believe that one day you\n"
2582 "would defeat even the mighty Huang Ming Pong! A hero such as you is bound\n"
2583 "to become world famous, and can earn a fortune if wealthy sponsors are behind\n"
2584 "him. May I therefore propose a mutually profitable contract: I'll give you this\n"
2585 "nice shirt with my company's ad, and you'll wear it as you journey bravely to\n"
2586 "the unknown and fight epic battles against the limitless minions of evil. I'll\n"
2587 "reward you well when you return, depending on how much you have used it.\"";
2588 Player->GetStack()->AddItem(decosadshirt::Spawn());
2590 TextScreen(Msg);
2591 GetCurrentArea()->SendNewDrawRequest();
2592 DrawEverything();
2594 Player->EditNP(-25000);
2595 Player->CheckStarvationDeath(CONST_S("exhausted after controlling a mirror image for too long"));
2596 throw areachangerequest();
2597 return true;
2601 rain *game::ConstructGlobalRain () {
2602 return new rain(GlobalRainLiquid, static_cast<lsquare*>(GetSquareInLoad()), GlobalRainSpeed, MONSTER_TEAM, false);
2606 v2 game::GetSunLightDirectionVector () {
2607 int Index = Tick % 48000 / 1000;
2608 /* Should have the same sign as sin(PI * Index / 24) and XTable[Index] /
2609 YTable[Index] should equal roughly -tan(PI * Index / 24). Also, vector
2610 (XTable[Index], YTable[Index]) + P should not be a valid position of
2611 any possible level L for any P belonging to L. */
2612 static int XTable[48] = {
2613 0, 1000, 1000, 1000, 1000, 1000,
2614 1000, 1303, 1732, 2414, 3732, 7596,
2615 1000, 7596, 3732, 2414, 1732, 1303,
2616 1000, 1000, 1000, 1000, 1000, 1000,
2617 0, -1000, -1000, -1000, -1000, -1000,
2618 -1000, -1303, -1732, -2414, -3732, -7596,
2619 -1000, -7596, -3732, -2414, -1732, -1303,
2620 -1000, -1000, -1000, -1000, -1000, -1000 };
2621 /* Should have the same sign as -cos(PI * Index / 24) */
2622 static int YTable[48] = { -1000, -7596, -3732, -2414, -1732, -1303,
2623 -1000, -1000, -1000, -1000, -1000, -1000,
2624 0, 1000, 1000, 1000, 1000, 1000,
2625 1000, 1303, 1732, 2414, 3732, 7596,
2626 1000, 7596, 3732, 2414, 1732, 1303,
2627 1000, 1000, 1000, 1000, 1000, 1000,
2628 0, -1000, -1000, -1000, -1000, -1000,
2629 -1000, -1303, -1732, -2414, -3732, -7596 };
2630 return v2(XTable[Index], YTable[Index]);
2634 int game::CalculateMinimumEmitationRadius (col24 E) {
2635 int MaxElement = Max(GetRed24(E), GetGreen24(E), GetBlue24(E));
2636 return int(sqrt(double(MaxElement << 7) / LIGHT_BORDER - 120.));
2640 feuLong game::IncreaseSquarePartEmitationTicks () {
2641 if ((SquarePartEmitationTick += 2) == 0x100) {
2642 CurrentLevel->InitSquarePartEmitationTicks();
2643 SquarePartEmitationTick = 2;
2645 return SquarePartEmitationTick;
2649 bool game::Wish (character *Wisher, cchar *MsgSingle, cchar *MsgPair, bool canAbort) {
2650 for (;;) {
2651 festring oldDef = DefaultWish;
2652 festring Temp = DefaultQuestion(CONST_S("What do you want to wish for?"), DefaultWish);
2653 if (DefaultWish == "nothing" && canAbort) {
2654 DefaultWish = oldDef;
2655 return false;
2657 item *TempItem = protosystem::CreateItem(Temp, Wisher->IsPlayer());
2658 if (TempItem) {
2659 Wisher->GetStack()->AddItem(TempItem);
2660 TempItem->SpecialGenerationHandler();
2661 if (TempItem->HandleInPairs()) ADD_MESSAGE(MsgPair, TempItem->CHAR_NAME(PLURAL));
2662 else ADD_MESSAGE(MsgSingle, TempItem->CHAR_NAME(INDEFINITE));
2663 return true;
2669 festring game::DefaultQuestion (festring Topic, festring &Default, stringkeyhandler KeyHandler) {
2670 festring ShortDefault = Default;
2671 if (Default.GetSize() > 29) {
2672 ShortDefault.Resize(27);
2673 ShortDefault = ShortDefault << CONST_S("...");
2675 if (!Default.IsEmpty()) Topic << " [" << ShortDefault << ']';
2676 festring Answer = StringQuestion(Topic, WHITE, 0, 80, false, KeyHandler);
2677 if (Answer.IsEmpty()) Answer = Default;
2678 return Default = Answer;
2682 void game::GetTime (ivantime &Time) {
2683 Time.Hour = 12 + Tick / 2000;
2684 Time.Day = Time.Hour / 24 + 1;
2685 Time.Hour %= 24;
2686 Time.Min = Tick % 2000 * 60 / 2000;
2690 truth NameOrderer (character *C1, character *C2) {
2691 return festring::IgnoreCaseCompare(C1->GetName(UNARTICLED), C2->GetName(UNARTICLED));
2695 truth game::PolymorphControlKeyHandler (int Key, festring &String) {
2696 if (Key == '?') {
2697 felist List(CONST_S("List of known creatures and their intelligence requirements"));
2698 SetStandardListAttributes(List);
2699 List.SetPageLength(15);
2700 List.AddFlags(SELECTABLE);
2701 protosystem::CreateEverySeenCharacter(CharacterDrawVector);
2702 std::sort(CharacterDrawVector.begin(), CharacterDrawVector.end(), NameOrderer);
2703 List.SetEntryDrawer(CharacterEntryDrawer);
2704 std::vector<festring> StringVector;
2705 uInt c;
2706 for (c = 0; c < CharacterDrawVector.size(); ++c) {
2707 character *Char = CharacterDrawVector[c];
2708 if (Char->CanBeWished()) {
2709 festring Entry;
2710 Char->AddName(Entry, UNARTICLED);
2711 StringVector.push_back(Entry);
2712 int Req = Char->GetPolymorphIntelligenceRequirement();
2713 if (Char->IsSameAs(Player) || (Player->GetPolymorphBackup() && Player->GetPolymorphBackup()->IsSameAs(Char))) Req = 0;
2714 Entry << " (" << Req << ')';
2715 int Int = Player->GetAttribute(INTELLIGENCE);
2716 List.AddEntry(Entry, Req > Int ? RED : LIGHT_GRAY, 0, c);
2719 int Chosen = List.Draw();
2720 for (c = 0; c < CharacterDrawVector.size(); ++c) delete CharacterDrawVector[c];
2721 if (!(Chosen & FELIST_ERROR_BIT)) String = StringVector[Chosen];
2722 CharacterDrawVector.clear();
2723 return true;
2725 return false;
2729 outputfile &operator << (outputfile &SaveFile, const killdata &Value) {
2730 SaveFile << Value.Amount << Value.DangerSum << Value.Reason;
2731 return SaveFile;
2735 inputfile &operator >> (inputfile &SaveFile, killdata &Value) {
2736 SaveFile >> Value.Amount >> Value.DangerSum >> Value.Reason;
2737 return SaveFile;
2741 outputfile &operator << (outputfile &SaveFile, const killreason &Value) {
2742 SaveFile << Value.Amount << Value.String;
2743 return SaveFile;
2747 inputfile &operator >> (inputfile &SaveFile, killreason &Value) {
2748 SaveFile >> Value.Amount >> Value.String;
2749 return SaveFile;
2753 truth DistanceOrderer (character *C1, character *C2) {
2754 v2 PlayerPos = PLAYER->GetPos();
2755 v2 Pos1 = C1->GetPos();
2756 v2 Pos2 = C2->GetPos();
2757 int D1 = Max(abs(Pos1.X - PlayerPos.X), abs(Pos1.Y - PlayerPos.Y));
2758 int D2 = Max(abs(Pos2.X - PlayerPos.X), abs(Pos2.Y - PlayerPos.Y));
2759 if (D1 != D2) return D1 < D2;
2760 if (Pos1.Y != Pos2.Y) return Pos1.Y < Pos2.Y;
2761 return Pos1.X < Pos2.X;
2765 truth game::FillPetVector (cchar *Verb) {
2766 PetVector.clear();
2767 team *Team = GetTeam(PLAYER_TEAM);
2768 for (std::list<character*>::const_iterator i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i)
2769 if ((*i)->IsEnabled() && !(*i)->IsPlayer() && (*i)->CanBeSeenByPlayer()) PetVector.push_back(*i);
2770 if (PetVector.empty()) {
2771 ADD_MESSAGE("You don't detect any friends to %s.", Verb);
2772 return false;
2774 std::sort(PetVector.begin(), PetVector.end(), DistanceOrderer);
2775 LastPetUnderCursor = PetVector[0];
2776 return true;
2780 truth game::CommandQuestion () {
2781 if (!FillPetVector("command")) return false;
2782 character *Char;
2783 if (PetVector.size() == 1) Char = PetVector[0];
2784 else {
2785 v2 Pos = PetVector[0]->GetPos();
2786 Pos = PositionQuestion(CONST_S("Whom do you wish to command? [direction keys/'+'/'-'/'a'll/space/esc]"), Pos, &PetHandler, &CommandKeyHandler);
2787 if (Pos == ERROR_V2) return false;
2788 if (Pos == ABORT_V2) return true;
2789 Char = CurrentArea->GetSquare(Pos)->GetCharacter();
2790 if (!Char || !Char->CanBeSeenByPlayer()) {
2791 ADD_MESSAGE("You don't see anyone here to command.");
2792 return false;
2794 if (Char->IsPlayer()) {
2795 ADD_MESSAGE("You do that all the time.");
2796 return false;
2798 if (!Char->IsPet()) {
2799 ADD_MESSAGE("%s refuses to be commanded by you.", Char->CHAR_NAME(DEFINITE));
2800 return false;
2803 return Char->IssuePetCommands();
2807 void game::NameQuestion () {
2808 if (!FillPetVector("name")) return;
2809 if (PetVector.size() == 1) PetVector[0]->TryToName();
2810 else PositionQuestion(CONST_S("Who do you want to name? [direction keys/'+'/'-'/'n'ame/esc]"), PetVector[0]->GetPos(), &PetHandler, &NameKeyHandler);
2814 void game::PetHandler (v2 CursorPos) {
2815 character *Char = CurrentArea->GetSquare(CursorPos)->GetCharacter();
2816 if (Char && Char->CanBeSeenByPlayer() && Char->IsPet() && !Char->IsPlayer()) CursorData = RED_CURSOR|CURSOR_TARGET;
2817 else CursorData = RED_CURSOR;
2818 if (Char && !Char->IsPlayer() && Char->IsPet()) LastPetUnderCursor = Char;
2822 v2 game::CommandKeyHandler (v2 CursorPos, int Key) {
2823 if (SelectPet(Key)) return LastPetUnderCursor->GetPos();
2824 if (Key == 'a' || Key == 'A') return CommandAll() ? ABORT_V2 : ERROR_V2;
2825 return CursorPos;
2829 truth game::SelectPet (int Key) {
2830 if (Key == '+') {
2831 for (uInt c = 0; c < PetVector.size(); ++c) {
2832 if (PetVector[c] == LastPetUnderCursor) {
2833 if (++c == PetVector.size()) c = 0;
2834 LastPetUnderCursor = PetVector[c];
2835 return true;
2838 } else if (Key == '-') {
2839 for (uInt c = 0; c < PetVector.size(); ++c) {
2840 if (PetVector[c] == LastPetUnderCursor) {
2841 if (!c) c = PetVector.size();
2842 LastPetUnderCursor = PetVector[--c];
2843 return true;
2847 return false;
2851 void game::CommandScreen (cfestring &Topic, feuLong PossibleFlags, feuLong ConstantFlags, feuLong &VaryFlags, feuLong &Flags) {
2852 static cchar *CommandDescription[COMMAND_FLAGS] = {
2853 "Follow me",
2854 "Flee from enemies",
2855 "Don't change your equipment",
2856 "Don't consume anything valuable"
2858 felist List(Topic);
2859 SetStandardListAttributes(List);
2860 List.AddFlags(SELECTABLE);
2861 List.AddDescription(CONST_S(""));
2862 List.AddDescription(CONST_S("Command Active?"));
2863 for (;;) {
2864 int c, i;
2865 for (c = 0; c < COMMAND_FLAGS; ++c) {
2866 if (1 << c & PossibleFlags) {
2867 truth Changeable = !(1 << c & ConstantFlags);
2868 festring Entry;
2869 if (Changeable) {
2870 Entry = CommandDescription[c];
2871 Entry.Resize(60);
2872 } else {
2873 Entry << " " << CommandDescription[c];
2874 Entry.Resize(63);
2876 if (1 << c & VaryFlags) Entry << "varies"; else Entry << (1 << c & Flags ? "yes" : "no");
2877 List.AddEntry(Entry, Changeable ? LIGHT_GRAY : DARK_GRAY, 0, NO_IMAGE, Changeable);
2880 int Chosen = List.Draw();
2881 if (Chosen & FELIST_ERROR_BIT) return;
2882 for (c = 0, i = 0; c < COMMAND_FLAGS; ++c) {
2883 if (1 << c & PossibleFlags && !(1 << c & ConstantFlags) && i++ == Chosen) {
2884 if (1 << c & VaryFlags) {
2885 VaryFlags &= ~(1 << c);
2886 Flags |= 1 << c;
2887 } else Flags ^= 1 << c;
2888 break;
2891 List.Empty();
2892 DrawEverythingNoBlit();
2897 truth game::CommandAll () {
2898 feuLong PossibleFlags = 0, ConstantFlags = ALL_COMMAND_FLAGS, VaryFlags = 0, OldFlags = 0;
2899 uInt c1, c2;
2900 for (c1 = 0; c1 < PetVector.size(); ++c1) {
2901 ConstantFlags &= PetVector[c1]->GetConstantCommandFlags();
2902 feuLong C = PetVector[c1]->GetCommandFlags();
2903 feuLong ThisPossible = PetVector[c1]->GetPossibleCommandFlags();
2904 for (c2 = 0; c2 < COMMAND_FLAGS; ++c2)
2905 if (1 << c2 & PossibleFlags & ThisPossible && (1 << c2 & C) != (1 << c2 & OldFlags)) VaryFlags |= 1 << c2;
2906 PossibleFlags |= ThisPossible;
2907 OldFlags |= C & ThisPossible;
2909 if (!PossibleFlags) {
2910 ADD_MESSAGE("Not a single creature in your visible team can be commanded.");
2911 return false;
2913 feuLong NewFlags = OldFlags;
2914 CommandScreen(CONST_S("Issue commands to whole visible team"), PossibleFlags, ConstantFlags, VaryFlags, NewFlags);
2915 truth Change = false;
2916 for (c1 = 0; c1 < PetVector.size(); ++c1) {
2917 character *Char = PetVector[c1];
2918 if (!Char->IsConscious()) continue;
2919 feuLong OldC = Char->GetCommandFlags();
2920 feuLong ConstC = Char->GetConstantCommandFlags();
2921 feuLong ThisC = (NewFlags & Char->GetPossibleCommandFlags() & ~(ConstC|VaryFlags)) | (OldC & (ConstC|VaryFlags));
2922 if (ThisC != OldC) Change = true;
2923 Char->SetCommandFlags(ThisC);
2925 if (!Change) return false;
2926 Player->EditAP(-500);
2927 Player->EditExperience(CHARISMA, 50, 1 << 7);
2928 return true;
2932 col16 game::GetAttributeColor (int I) {
2933 int Delta = GetTick()-LastAttributeChangeTick[I];
2934 if (OldAttribute[I] == NewAttribute[I] || Delta >= 510) return WHITE;
2935 if (OldAttribute[I] < NewAttribute[I]) return MakeRGB16(255, 255, Delta >> 1);
2936 return MakeRGB16(255, Delta >> 1, Delta >> 1);
2940 void game::UpdateAttributeMemory () {
2941 for (int c = 0; c < ATTRIBUTES; ++c) {
2942 int A = Player->GetAttribute(c);
2943 if (A != NewAttribute[c]) {
2944 OldAttribute[c] = NewAttribute[c];
2945 NewAttribute[c] = A;
2946 LastAttributeChangeTick[c] = GetTick();
2952 void game::InitAttributeMemory () {
2953 for (int c = 0; c < ATTRIBUTES; ++c) OldAttribute[c] = NewAttribute[c] = Player->GetAttribute(c);
2957 void game::TeleportHandler (v2 CursorPos) {
2958 if ((CursorPos-Player->GetPos()).GetLengthSquare() > Player->GetTeleportRangeSquare())
2959 CursorData = BLUE_CURSOR|CURSOR_TARGET;
2960 else
2961 CursorData = RED_CURSOR|CURSOR_TARGET;
2965 double game::GetGameSituationDanger () {
2966 double SituationDanger = 0;
2967 character *Player = GetPlayer();
2968 truth PlayerStuck = Player->IsStuck();
2969 v2 PlayerPos = Player->GetPos();
2970 character *TruePlayer = Player;
2971 if (PlayerStuck) (Player = Player->Duplicate(IGNORE_PROHIBITIONS))->ChangeTeam(0);
2972 for (int c1 = 0; c1 < GetTeams(); ++c1)
2973 if (GetTeam(c1)->GetRelation(GetTeam(PLAYER_TEAM)) == HOSTILE)
2974 for (std::list<character*>::const_iterator i1 = GetTeam(c1)->GetMember().begin(); i1 != GetTeam(c1)->GetMember().end(); ++i1) {
2975 character *Enemy = *i1;
2976 if (Enemy->IsEnabled() && Enemy->CanAttack() && (Enemy->CanMove() || Enemy->GetPos().IsAdjacent(PlayerPos))) {
2977 truth EnemyStuck = Enemy->IsStuck();
2978 v2 EnemyPos = Enemy->GetPos();
2979 truth Sees = TruePlayer->CanBeSeenBy(Enemy);
2980 character *TrueEnemy = Enemy;
2981 if (EnemyStuck) Enemy = Enemy->Duplicate(IGNORE_PROHIBITIONS);
2982 double PlayerTeamDanger = 1/Enemy->GetSituationDanger(Player, EnemyPos, PlayerPos, Sees);
2983 for (int c2 = 0; c2 < GetTeams(); ++c2)
2984 if (GetTeam(c2)->GetRelation(GetTeam(c1)) == HOSTILE)
2985 for (std::list<character*>::const_iterator i2 = GetTeam(c2)->GetMember().begin(); i2 != GetTeam(c2)->GetMember().end(); ++i2) {
2986 character *Friend = *i2;
2987 if (Friend->IsEnabled() && !Friend->IsPlayer() && Friend->CanAttack() && (Friend->CanMove() || Friend->GetPos().IsAdjacent(EnemyPos))) {
2988 v2 FriendPos = Friend->GetPos();
2989 truth Sees = TrueEnemy->CanBeSeenBy(Friend);
2990 if (Friend->IsStuck()) {
2991 Friend = Friend->Duplicate(IGNORE_PROHIBITIONS);
2992 PlayerTeamDanger += Friend->GetSituationDanger(Enemy, FriendPos, EnemyPos, Sees) * .2;
2993 delete Friend;
2994 } else PlayerTeamDanger += Friend->GetSituationDanger(Enemy, FriendPos, EnemyPos, Sees);
2997 if (EnemyStuck) {
2998 PlayerTeamDanger *= 5;
2999 delete Enemy;
3001 SituationDanger += 1 / PlayerTeamDanger;
3004 Player->ModifySituationDanger(SituationDanger);
3005 if (PlayerStuck) {
3006 SituationDanger *= 2;
3007 delete Player;
3009 return SituationDanger;
3013 sLong game::GetTimeSpent () {
3014 return time::TimeAdd(time::TimeDifference(time(0),LastLoad), TimePlayedBeforeLastLoad);
3018 outputfile &operator << (outputfile &SaveFile, const massacreid &MI) {
3019 SaveFile << MI.Type << MI.Config << MI.Name;
3020 return SaveFile;
3024 inputfile &operator >> (inputfile &SaveFile, massacreid &MI) {
3025 SaveFile >> MI.Type >> MI.Config >> MI.Name;
3026 return SaveFile;
3030 truth game::PlayerIsRunning () {
3031 return PlayerRunning && Player->CanMove();
3035 void game::AddSpecialCursor (v2 Pos, int Data) {
3036 SpecialCursorPos.push_back(Pos);
3037 SpecialCursorData.push_back(Data);
3041 void game::RemoveSpecialCursors () {
3042 SpecialCursorPos.clear();
3043 SpecialCursorData.clear();
3047 void game::LearnAbout (god *Who) {
3048 Who->SetIsKnown(true);
3049 /* slightly slow, but doesn't matter since this is run so rarely */
3050 if (PlayerKnowsAllGods() && !game::PlayerHasReceivedAllGodsKnownBonus) {
3051 GetPlayer()->ApplyAllGodsKnownBonus();
3052 game::PlayerHasReceivedAllGodsKnownBonus = true;
3057 truth game::PlayerKnowsAllGods () {
3058 for (int c = 1; c <= GODS; ++c) if (!GetGod(c)->IsKnown()) return false;
3059 return true;
3063 void game::AdjustRelationsToAllGods (int Amount) {
3064 for (int c = 1; c <= GODS; ++c) GetGod(c)->AdjustRelation(Amount);
3068 void game::SetRelationsToAllGods (int Amount) {
3069 for (int c = 1; c <= GODS; ++c) GetGod(c)->SetRelation(Amount);
3073 void game::ShowDeathSmiley (bitmap *Buffer, truth) {
3074 static blitdata B = {
3076 { 0, 0 },
3077 { (RES.X >> 1) - 24, RES.Y * 4 / 7 - 24 },
3078 { 48, 48 },
3079 { 0 },
3080 TRANSPARENT_COLOR,
3083 int Tick = globalwindowhandler::UpdateTick();
3084 if (((Tick >> 1) & 31) == 1) B.Src.X = 48;
3085 else if (((Tick >> 1) & 31) == 2) B.Src.X = 96;
3086 else B.Src.X = 0;
3087 B.Bitmap = Buffer;
3088 igraph::GetSmileyGraphic()->NormalBlit(B);
3089 if (Buffer == DOUBLE_BUFFER) graphics::BlitDBToScreen();
3093 static int doListSelector (felist &list, int defsel, int cnt) {
3094 game::SetStandardListAttributes(list);
3095 list.AddFlags(SELECTABLE | FELIST_NO_BADKEY_EXIT);
3096 if (defsel > 0) list.SetSelected(defsel);
3097 uInt sel = list.Draw();
3098 list.Empty();
3099 list.RemoveFlags(SELECTABLE | FELIST_NO_BADKEY_EXIT);
3100 if (sel & FELIST_ERROR_BIT) return -1;
3101 if (sel >= (uInt)cnt) return -1;
3102 return (int)sel;
3106 int game::ListSelector (int defsel, cfestring &title, ...) {
3107 int cnt = 0;
3108 va_list items;
3109 va_start(items, title);
3111 felist list(title);
3112 for (;;) {
3113 const char *s = va_arg(items, const char *);
3114 if (!s) break;
3115 list.AddEntry(s, LIGHT_GRAY);
3116 cnt++;
3118 va_end(items);
3119 return doListSelector(list, defsel, cnt);
3123 int game::ListSelectorArray (int defsel, cfestring &title, const char *items[]) {
3124 int cnt = 0;
3125 felist list(title);
3126 for (;;) {
3127 if (!items[cnt]) break;
3128 list.AddEntry(items[cnt], LIGHT_GRAY);
3129 cnt++;
3131 return doListSelector(list, defsel, cnt);
3135 void game::ClearEventData () {
3136 mChar = 0;
3137 mActor = 0;
3138 mSecondActor = 0;
3139 mItem = 0;
3143 // '.': string or number
3144 // 'n': number
3145 // 's': string
3146 // '*': collect all args
3147 int game::ParseFuncArgs (cfestring &types, std::vector<FuncArg> &args, inputfile *fl, truth noterm) {
3148 festring s;
3149 sLong n;
3150 truth isStr;
3151 if (!fl) fl = mFEStack.top();
3152 args.clear();
3153 for (unsigned int f = 0; f < types.GetSize(); f++) {
3154 switch (types[f]) {
3155 case '.':
3156 s = fl->ReadStringOrNumber(&n, &isStr, true);
3157 if (isStr) args.push_back(FuncArg(s)); else args.push_back(FuncArg(n));
3158 break;
3159 case 'n':
3160 n = fl->ReadNumber(0xFF, true);
3161 args.push_back(FuncArg(n));
3162 break;
3163 case '*':
3164 for (;;) {
3165 s = fl->ReadStringOrNumber(&n, &isStr, true);
3166 if (isStr) args.push_back(FuncArg(s)); else args.push_back(FuncArg(n));
3167 fl->ReadWord(s, true);
3168 if (s == ";") return args.size();
3169 if (s != ",") ABORT("',' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3171 // never reached
3172 case 's':
3173 default:
3174 s = fl->ReadWord(true);
3175 args.push_back(FuncArg(s));
3176 break;
3178 if (f == types.GetSize()-1) {
3179 if (noterm) break;
3180 fl->ReadWord(s, true);
3181 if (s != ";") ABORT("';' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3182 break;
3183 } else {
3184 fl->ReadWord(s, true);
3185 if (s != ",") ABORT("',' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3188 return args.size();
3192 truth game::GetWord (festring &w) {
3193 for (;;) {
3194 inputfile *fl = mFEStack.top();
3195 fl->ReadWord(w, false);
3196 if (w == "" && fl->Eof()) {
3197 delete fl;
3198 mFEStack.pop();
3199 if (mFEStack.empty()) return false;
3200 continue;
3202 if (w == "Include") {
3203 fl->ReadWord(w, true);
3204 if (fl->ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3205 w = game::GetGameDir()+"Script/"+w;
3206 inputfile *fl = new inputfile(w, &game::GetGlobalValueMap(), true);
3207 fl->setGetVarCB(game::ldrGetVar);
3208 mFEStack.push(fl);
3209 continue;
3211 if (w == "Message") {
3212 fl->ReadWord(w, true);
3213 if (fl->ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3214 fprintf(stderr, "MESSAGE: %s\n", w.CStr());
3215 continue;
3217 return true;
3222 void game::SkipBlock (truth brcEaten) {
3223 festring w;
3224 if (!brcEaten) {
3225 mFEStack.top()->ReadWord(w, true);
3226 if (w != "{") ABORT("'{' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3228 int cnt = 1;
3229 for (;;) {
3230 mFEStack.top()->ReadWord(w, true);
3231 if (w == "{") cnt++;
3232 else if (w == "}") {
3233 if (--cnt < 1) break;
3239 truth game::DoOnEvent (truth brcEaten, truth AllowScript) {
3240 // do; only funcalls for now
3241 truth eaten = AllowScript ? true : false;
3242 festring w;
3243 if (!brcEaten) {
3244 mFEStack.top()->ReadWord(w, true);
3245 if (w != "{") ABORT("'{' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3247 for (;;) {
3248 if (!GetWord(w)) {
3249 if (AllowScript) break;
3250 ABORT("Unexpected end of file %s!", mFEStack.top()->GetFileName().CStr());
3252 //fprintf(stderr, " :[%s]\n", w.CStr());
3253 if (w == "}") {
3254 if (AllowScript) ABORT("Unexpected '}' in AllowScript file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3255 break;
3257 if (w == ";") continue;
3258 if (w == "@") {
3259 mFEStack.top()->ReadWord(w, true);
3260 if (mFEStack.top()->ReadWord(true) != "=") ABORT("'=' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3261 //fprintf(stderr, "setvar: %s\n", w.CStr());
3262 if (w == "money") {
3263 sLong n = mFEStack.top()->ReadNumber(true);
3264 if (n < 0) n = 0;
3265 if (mChar) mChar->SetMoney(n);
3266 continue;
3268 if (w == "result") {
3269 mResult = mFEStack.top()->ReadNumber(true);
3270 continue;
3272 ABORT("Unknown var [%s] in file %s at line %d!", w.CStr(), mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3273 } else {
3274 //mFEStack.top()->ReadWord(w, true);
3275 std::vector<FuncArg> args;
3276 //fprintf(stderr, "funcall: %s\n", w.CStr());
3277 if (w == "SetMoney") {
3278 ParseFuncArgs("n", args);
3279 sLong n = args[0].ival;
3280 if (n < 0) n = 0;
3281 if (mChar) mChar->SetMoney(n);
3282 continue;
3284 if (w == "EditMoney") {
3285 ParseFuncArgs("n", args);
3286 sLong n = args[0].ival;
3287 if (mChar) mChar->EditMoney(n);
3288 continue;
3290 if (w == "AddMessage") {
3291 ParseFuncArgs("*", args);
3292 festring s;
3293 for (uInt f = 0; f < args.size(); f++) {
3294 const FuncArg &a = args[f];
3295 if (a.type == FARG_STRING) s << a.sval; else s << a.ival;
3297 ADD_MESSAGE("%s", s.CStr());
3298 continue;
3300 if (w == "EatThisEvent") {
3301 if (AllowScript) ABORT("'EatThisEvent' forbidden in AllowScripts in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3302 eaten = true;
3303 continue;
3305 if (w == "Disallow") {
3306 if (!AllowScript) ABORT("'Disallow' forbidden in not-AllowScripts in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3307 eaten = false;
3308 continue;
3310 ABORT("Unknown function [%s] in file %s at line %d!", w.CStr(), mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3311 //if (mFEStack.top()->ReadWord() != ";") ABORT("';' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3313 //ABORT("Invalid term in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3315 //fprintf(stderr, "------------\n");
3316 return eaten;
3320 //TODO: cache event scripts
3321 truth game::RunOnEvent (cfestring &ename) {
3322 static std::vector<festring> scriptFiles;
3323 static truth cached = false;
3324 truth res = false;
3326 character *old = mChar;
3327 mChar = PLAYER;
3328 if (!cached) {
3329 cached = true;
3330 for (int fno = 99; fno >= -1; fno--) {
3331 festring cfname;
3332 cfname << game::GetGameDir() << "Script/onevent";
3333 if (fno >= 0) {
3334 char bnum[8];
3335 sprintf(bnum, "_%02d", fno);
3336 cfname << bnum;
3338 cfname << ".dat";
3339 if (!inputfile::fileExists(cfname)) continue;
3340 inputfile *ifl = new inputfile(cfname, &game::GetGlobalValueMap(), false);
3341 if (!ifl->IsOpen()) {
3342 delete ifl;
3343 continue;
3345 scriptFiles.push_back(cfname);
3346 ifl->setGetVarCB(game::ldrGetVar);
3347 mFEStack.push(ifl);
3349 } else {
3350 for (unsigned int f = 0; f < scriptFiles.size(); ++f) {
3351 festring cfname = scriptFiles[f];
3352 inputfile *ifl = new inputfile(cfname, &game::GetGlobalValueMap(), false);
3353 if (!ifl->IsOpen()) {
3354 delete ifl;
3355 continue;
3357 ifl->setGetVarCB(game::ldrGetVar);
3358 mFEStack.push(ifl);
3362 festring w;
3363 while (GetWord(w)) {
3364 if (w != "on") ABORT("'on' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3365 mFEStack.top()->ReadWord(w, true);
3366 truth doIt = (w==ename);
3367 if (doIt && !res) {
3368 res = DoOnEvent(false);
3369 } else {
3370 // skip
3371 SkipBlock(false);
3374 mChar = old;
3375 return res;
3379 truth game::RunOnEventStr (cfestring &ename, cfestring &str) {
3380 truth res = false;
3381 if (str.GetSize() < 1) return false;
3382 //fprintf(stderr, "=============\n%s=============\n", str.CStr());
3383 inputfile *ifl = new meminputfile(str, &game::GetGlobalValueMap());
3384 ifl->setGetVarCB(game::ldrGetVar);
3385 mFEStack.push(ifl);
3386 festring w;
3387 //fprintf(stderr, "=============\n", str.CStr());
3388 //fprintf(stderr, "event: [%s]\n", ename.CStr());
3389 //fprintf(stderr, "---\n%s---\n", str.CStr());
3390 while (GetWord(w)) {
3391 if (w != "on") ABORT("'on' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3392 mFEStack.top()->ReadWord(w, true);
3393 //fprintf(stderr, "on: [%s]\n", w.CStr());
3394 truth doIt = (w==ename);
3395 if (doIt && !res) {
3396 //fprintf(stderr, " do it\n");
3397 res = DoOnEvent(false);
3398 } else {
3399 // skip
3400 //fprintf(stderr, " skip it\n");
3401 SkipBlock(false);
3404 return res;
3408 truth game::RunOnCharEvent (character *who, cfestring &ename) {
3409 truth res = false;
3410 if (!who) return false;
3411 character *old = mChar;
3412 mChar = who;
3413 res = RunOnEventStr(ename, who->mOnEvents);
3414 if (!res) res = RunOnEventStr(ename, who->GetProtoType()->mOnEvents);
3415 mChar = old;
3416 return res;
3420 truth game::RunOnItemEvent (item *what, cfestring &ename) {
3421 truth res = false;
3422 if (!what) return false;
3423 item *old = mItem;
3424 mItem = what;
3425 res = RunOnEventStr(ename, what->mOnEvents);
3426 if (!res) res = RunOnEventStr(ename, what->GetProtoType()->mOnEvents);
3427 mItem = old;
3428 return res;
3432 festring game::ldrGetVar (inputfile *fl, cfestring &name) {
3433 //fprintf(stderr, "GETVAR: [%s]\n", name.CStr());
3434 if (name == "player_name") {
3435 return game::GetPlayerName();
3437 if (name == "money") {
3438 festring res;
3439 if (!mChar) return "0";
3440 res << mChar->GetMoney();
3441 return res;
3443 if (name == "name") {
3444 if (!mChar) return "";
3445 return mChar->GetAssignedName();
3447 if (name == "team") {
3448 festring res;
3449 if (!mChar) return "";
3450 res << mChar->GetTeam()->GetID();
3451 return res;
3453 if (name == "friendly") {
3454 festring res;
3455 if (!mChar || !PLAYER || mChar->GetRelation(PLAYER) != HOSTILE) return "tan";
3456 return "";
3458 if (name == "hostile") {
3459 festring res;
3460 if (!mChar || !PLAYER) return "";
3461 if (mChar->GetRelation(PLAYER) == HOSTILE) return "tan";
3462 return "";
3464 if (name == "has_item") {
3465 std::vector<FuncArg> args;
3466 ParseFuncArgs("s", args, fl, true);
3468 if (PLAYER) {
3469 itemvector items;
3470 festring s = args[0].sval;
3472 //fprintf(stderr, "looking for [%s]\n", s.CStr());
3473 PLAYER->GetStack()->FillItemVector(items);
3474 for (unsigned int f = 0; f < items.size(); ++f) {
3475 for (uInt c = 0; c < items[f]->GetDataBase()->Alias.Size; ++c) {
3476 //fprintf(stderr, "%u:%u: [%s]\n", f, c, items[f]->GetDataBase()->Alias[c].CStr());
3477 if (s.CompareIgnoreCase(items[f]->GetDataBase()->Alias[c]) == 0) {
3478 //fprintf(stderr, " FOUND!\n");
3479 return "tan";
3483 //fprintf(stderr, "checking equipment...\n");
3484 for (int f = 0; f < PLAYER->GetEquipments(); ++f) {
3485 item *it = PLAYER->GetEquipment(f);
3487 if (it) {
3488 for (uInt c = 0; c < it->GetDataBase()->Alias.Size; ++c) {
3489 //fprintf(stderr, "%u:%u: [%s]\n", f, c, it->GetDataBase()->Alias[c].CStr());
3490 if (s.CompareIgnoreCase(it->GetDataBase()->Alias[c]) == 0) {
3491 //fprintf(stderr, " FOUND!\n");
3492 return "tan";
3498 return "";
3500 //if (name == "type") return mVarType;
3501 ABORT("unknown variable: %s", name.CStr());
3502 return "";
3506 truth game::CheckDropLeftover (item *i) {
3507 if (i->IsBottle() && !ivanconfig::GetAutoDropBottles()) return false;
3508 if (i->IsCan() && !ivanconfig::GetAutoDropCans()) return false;
3509 if (!ivanconfig::GetAutoDropLeftOvers()) return false;
3510 return true;
3514 truth game::RunAllowScriptStr (cfestring &str) {
3515 truth res = true;
3516 if (str.GetSize() < 1) return true;
3517 //fprintf(stderr, "====\n%s\n====\n", str.CStr());
3518 inputfile *ifl = new meminputfile(str, &game::GetGlobalValueMap());
3519 ifl->setGetVarCB(game::ldrGetVar);
3520 mFEStack.push(ifl);
3521 res = DoOnEvent(true, true);
3522 //fprintf(stderr, "mFEStack: %u\n", mFEStack.size());
3523 return res;