fixed some bugs in new 'g'o system
[k8-i-v-a-n.git] / src / game / game.cpp
blobd36db4aec8dac51b3ab877b0ece6acc16744e077
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 128 // Increment this if changes make savefiles incompatible
54 #define BONE_FILE_VERSION 113 // 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 truth game::WizardMode;
178 int game::SeeWholeMapCheatMode;
179 truth game::GoThroughWallsCheat;
180 int game::QuestMonstersFound;
181 bitmap *game::BusyAnimationCache[32];
182 festring game::PlayerName;
183 feuLong game::EquipmentMemory[MAX_EQUIPMENT_SLOTS];
184 olterrain *game::MonsterPortal;
185 std::vector<v2> game::SpecialCursorPos;
186 std::vector<int> game::SpecialCursorData;
187 cbitmap *game::EnterImage;
188 v2 game::EnterTextDisplacement;
191 // -1: none
192 int game::MoveVectorToDirection (cv2 &mv) {
193 for (int c = 0; c < 9; ++c) if (MoveVector[c] == mv) return c;
194 return -1;
198 char game::GetAbnormalMoveKey (int idx) {
199 if (idx < 0 || idx > 8) return 0;
200 return MoveAbnormalCommandKey[idx];
204 void game::SetAbnormalMoveKey (int idx, char ch) {
205 if (idx >= 0 && idx <= 8) MoveAbnormalCommandKey[idx] = ch;
209 void game::AddCharacterID (character *Char, feuLong ID) {
210 /*k8:??? if (CharacterIDMap.find(ID) != CharacterIDMap.end())
211 int esko = esko = 2;*/
212 CharacterIDMap.insert(std::make_pair(ID, Char));
216 void game::RemoveCharacterID (feuLong ID) {
217 /*k8:??? if (CharacterIDMap.find(ID) == CharacterIDMap.end())
218 int esko = esko = 2;*/
219 CharacterIDMap.erase(CharacterIDMap.find(ID));
223 void game::AddItemID (item *Item, feuLong ID) {
224 /*k8:??? if (ItemIDMap.find(ID) != ItemIDMap.end())
225 int esko = esko = 2;*/
226 ItemIDMap.insert(std::make_pair(ID, Item));
230 void game::RemoveItemID (feuLong ID) {
231 /*k8:??? if(ID && ItemIDMap.find(ID) == ItemIDMap.end())
232 int esko = esko = 2;*/
233 if (ID) ItemIDMap.erase(ItemIDMap.find(ID));
237 void game::UpdateItemID (item *Item, feuLong ID) {
238 /*k8:??? if(ItemIDMap.find(ID) == ItemIDMap.end())
239 int esko = esko = 2;*/
240 ItemIDMap.find(ID)->second = Item;
244 void game::AddTrapID (entity *Trap, feuLong ID) {
245 /*k8:??? if(TrapIDMap.find(ID) != TrapIDMap.end())
246 int esko = esko = 2;*/
247 if (ID) TrapIDMap.insert(std::make_pair(ID, Trap));
251 void game::RemoveTrapID (feuLong ID) {
252 /*k8:??? if(ID && TrapIDMap.find(ID) == TrapIDMap.end())
253 int esko = esko = 2;*/
254 if (ID) TrapIDMap.erase(TrapIDMap.find(ID));
258 void game::UpdateTrapID (entity *Trap, feuLong ID) {
259 /*k8:??? if(TrapIDMap.find(ID) == TrapIDMap.end())
260 int esko = esko = 2;*/
261 TrapIDMap.find(ID)->second = Trap;
265 const dangermap &game::GetDangerMap () { return DangerMap; }
266 void game::ClearItemDrawVector () { ItemDrawVector.clear(); }
267 void game::ClearCharacterDrawVector () { CharacterDrawVector.clear(); }
270 void game::InitScript () {
271 inputfile ScriptFile(GetGameDir()+"Script/dungeon.dat", &GlobalValueMap);
272 GameScript = new gamescript;
273 GameScript->ReadFrom(ScriptFile);
274 { /* additional dungeon files */
275 for (int f = 0; f <= 99; f++) {
276 char bnum[32];
277 sprintf(bnum, "Script/dungeon_%02d.dat", f);
278 inputfile ifl(game::GetGameDir()+bnum, &game::GetGlobalValueMap(), false);
279 if (ifl.IsOpen()) {
280 //fprintf(stderr, "loading: %s\n", bnum+7);
281 GameScript->ReadFrom(ifl);
282 ifl.Close();
286 GameScript->RandomizeLevels();
290 truth game::Init (cfestring &Name) {
291 if (Name.IsEmpty()) {
292 if (ivanconfig::GetDefaultName().IsEmpty()) {
293 PlayerName.Empty();
294 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;
295 } else {
296 PlayerName = ivanconfig::GetDefaultName();
298 } else {
299 PlayerName = Name;
302 #ifndef WIN32
303 mkdir(GetSaveDir().CStr(), S_IRWXU|S_IRWXG);
304 mkdir(GetBoneDir().CStr(), S_IRWXU|S_IRWXG);
305 #else
306 mkdir(GetSaveDir().CStr());
307 mkdir(GetBoneDir().CStr());
308 #endif
310 ::InitPlaces();
311 LOSTick = 2;
312 DangerFound = 0;
313 CausePanicFlag = false;
314 pool::KillBees();
315 //???
316 switch (Load(SaveName(PlayerName))) {
317 case LOADED: {
318 globalwindowhandler::InstallControlLoop(AnimationController);
319 SetIsRunning(true);
320 SetForceJumpToPlayerBe(true);
321 GetCurrentArea()->SendNewDrawRequest();
322 SendLOSUpdateRequest();
323 ADD_MESSAGE("Game loaded successfully.");
324 } return true;
325 case NEW_GAME: {
326 iosystem::TextScreen(CONST_S(
327 "You couldn't possibly have guessed this day would differ from any other.\n"
328 "It began just as always. You woke up at dawn and drove off the giant spider\n"
329 "resting on your face. On your way to work you had serious trouble avoiding\n"
330 "the lions and pythons roaming wild around the village. After getting kicked\n"
331 "by colony masters for being late you performed your twelve-hour routine of\n"
332 "climbing trees, gathering bananas, climbing trees, gathering bananas, chasing\n"
333 "monkeys that stole the first gathered bananas, carrying bananas to the village\n"
334 "and trying to look happy when real food was distributed.\n\n"
335 "Finally you were about to enjoy your free time by taking a quick dip in the\n"
336 "nearby crocodile bay. However, at this point something unusual happened.\n"
337 "You were summoned to the mansion of Richel Decos, the viceroy of the\n"
338 "colony, and were led directly to him."));
339 iosystem::TextScreen(CONST_S(
340 "\"I have a task for you, citizen\", said the viceroy picking his golden\n"
341 "teeth, \"The market price of bananas has taken a deep dive and yet the\n"
342 "central government is about to raise taxes. I have sent appeals to high\n"
343 "priest Petrus but received no response. I fear my enemies in Attnam are\n"
344 "plotting against me and intercepting my messages before they reach him!\"\n\n"
345 "\"That is why you must travel to Attnam with a letter I'll give you and\n"
346 "deliver it to Petrus directly. Alas, you somehow have to cross the sea\n"
347 "between. Because it's winter, all Attnamese ships are trapped by ice and\n"
348 "I have none. Therefore you must venture through the small underwater tunnel\n"
349 "connecting our islands. It is infested with monsters, but since you have\n"
350 "stayed alive here so long, the trip will surely cause you no trouble.\"\n\n"
351 "You have never been so happy! According to the mansion's traveling\n"
352 "brochures, Attnam is a peaceful but bustling world city on a beautiful\n"
353 "snowy fell surrounded by frozen lakes glittering in the arctic sun just\n"
354 "like the diamonds of the imperial treasury. Not that you would believe a\n"
355 "word. The point is that tomorrow you can finally forget your home and\n"
356 "face the untold adventures ahead."));
357 globalwindowhandler::InstallControlLoop(AnimationController);
358 LOSTick = 2;
359 DangerFound = 0;
360 CausePanicFlag = false;
361 SetIsRunning(true);
362 InWilderness = true;
363 iosystem::TextScreen(CONST_S("Generating game...\n\nThis may take some time, please wait."), ZERO_V2, WHITE, false, true, &BusyAnimation);
364 igraph::CreateBackGround(GRAY_FRACTAL);
365 NextCharacterID = 1;
366 NextItemID = 1;
367 NextTrapID = 1;
368 InitScript();
369 CreateTeams();
370 CreateGods();
371 SetPlayer(playerkind::Spawn());
372 Player->SetAssignedName(PlayerName);
373 Player->SetTeam(GetTeam(0));
374 Player->SetNP(SATIATED_LEVEL);
375 for (int c = 0; c < ATTRIBUTES; ++c) {
376 if (c != ENDURANCE) Player->EditAttribute(c, (RAND()&1)-(RAND()&1));
377 Player->EditExperience(c, 500, 1<<11);
379 Player->SetMoney(Player->GetMoney()+RAND()%11);
380 GetTeam(0)->SetLeader(Player);
381 InitDangerMap();
383 pool::KillBees();
384 if (Player->IsEnabled()) { Player->Disable(); Player->Enable(); }
386 Petrus = 0;
387 InitDungeons();
388 SetCurrentArea(WorldMap = new worldmap(128, 128));
389 CurrentWSquareMap = WorldMap->GetMap();
390 WorldMap->Generate();
391 UpdateCamera();
392 SendLOSUpdateRequest();
393 Tick = 0;
394 Turn = 0;
395 InitPlayerAttributeAverage();
396 StoryState = 0;
397 /* */
398 MondedrPass = 0;
399 RingOfThieves = 0;
400 Masamune = 0;
401 Muramasa = 0;
402 LoricatusHammer = 0;
403 Liberator = 0;
404 OmmelBloodMission = 0;
405 RegiiTalkState = 0;
406 /* */
407 PlayerMassacreMap.clear();
408 PetMassacreMap.clear();
409 MiscMassacreMap.clear();
410 PlayerMassacreAmount = PetMassacreAmount = MiscMassacreAmount = 0;
411 DefaultPolymorphTo.Empty();
412 DefaultSummonMonster.Empty();
413 DefaultWish.Empty();
414 DefaultChangeMaterial.Empty();
415 DefaultDetectMaterial.Empty();
416 Player->GetStack()->AddItem(encryptedscroll::Spawn());
417 if (ivanconfig::GetDefaultPetName() != "_none_") {
418 character *Doggie = dog::Spawn();
419 Doggie->SetTeam(GetTeam(0));
420 GetWorldMap()->GetPlayerGroup().push_back(Doggie);
421 Doggie->SetAssignedName(ivanconfig::GetDefaultPetName());
423 WizardMode = false;
424 SeeWholeMapCheatMode = MAP_HIDDEN;
425 GoThroughWallsCheat = false;
426 SumoWrestling = false;
427 GlobalRainTimeModifier = 2048-(RAND()&4095);
428 PlayerSumoChampion = false;
429 PlayerSolicitusChampion = false;
430 protosystem::InitCharacterDataBaseFlags();
431 memset(EquipmentMemory, 0, sizeof(EquipmentMemory));
432 PlayerRunning = false;
433 InitAttributeMemory();
434 NecroCounter = 0;
435 GameBegan = time(0);
436 LastLoad = time(0);
437 TimePlayedBeforeLastLoad = time::GetZeroTime();
438 /*k8: damn! seems that this is field, not local! bool PlayerHasReceivedAllGodsKnownBonus = false; */
439 PlayerHasReceivedAllGodsKnownBonus = false;
440 ADD_MESSAGE("You commence your journey to Attnam. Use direction keys to move, '>' to enter an area and '?' to view other commands.");
441 game::ClearEventData();
442 RunOnEvent("game_start");
443 if (IsXMas()) {
444 item *Present = banana::Spawn();
445 Player->GetStack()->AddItem(Present);
446 ADD_MESSAGE("Atavus is happy today! He gives you %s.", Present->CHAR_NAME(INDEFINITE));
448 } return true;
449 default: return false;
454 void game::DeInit () {
455 pool::BurnHell();
456 delete WorldMap;
457 WorldMap = 0;
458 if (Dungeon) {
459 for (int c = 1; c < Dungeons; ++c) delete Dungeon[c];
460 delete [] Dungeon;
461 Dungeon = 0;
463 if (God) {
464 for (int c = 1; c <= GODS; ++c) delete God[c]; // sorry, Valpuri!
465 delete [] God;
466 God = 0;
468 if (Team) {
469 for (int c = 0; c < Teams; ++c) delete Team[c];
470 delete [] Team;
471 Team = 0;
473 delete GameScript;
474 GameScript = 0;
475 msgsystem::Format();
476 DangerMap.clear();
480 void game::Run () {
481 for (;;) {
482 if (!InWilderness) {
483 /* Temporary places */
484 static int Counter = 0;
485 if (++Counter == 10) {
486 CurrentLevel->GenerateMonsters();
487 Counter = 0;
489 if (CurrentDungeonIndex == ELPURI_CAVE && CurrentLevelIndex == ZOMBIE_LEVEL && !RAND_N(1000+NecroCounter)) {
490 character *Char = necromancer::Spawn(RAND_N(4) ? APPRENTICE_NECROMANCER : MASTER_NECROMANCER);
491 v2 Pos;
492 for (int c2 = 0; c2 < 30; ++c2) {
493 Pos = GetCurrentLevel()->GetRandomSquare(Char);
494 if (abs(int(Pos.X)-Player->GetPos().X) > 20 || abs(int(Pos.Y)-Player->GetPos().Y) > 20) break;
496 if (Pos != ERROR_V2) {
497 Char->SetTeam(GetTeam(MONSTER_TEAM));
498 Char->PutTo(Pos);
499 Char->SetGenerationDanger(GetCurrentLevel()->GetDifficulty());
500 Char->SignalGeneration();
501 Char->SignalNaturalGeneration();
502 ivantime Time;
503 GetTime(Time);
504 int Modifier = Time.Day - EDIT_ATTRIBUTE_DAY_MIN;
505 if (Modifier > 0) Char->EditAllAttributes(Modifier >> EDIT_ATTRIBUTE_DAY_SHIFT);
506 NecroCounter += 50;
507 } else {
508 delete Char;
509 //Char->SendToHell(); // k8:equipment
513 if (!(GetTick() % 1000)) CurrentLevel->CheckSunLight();
515 if ((CurrentDungeonIndex == NEW_ATTNAM || CurrentDungeonIndex == ATTNAM) && CurrentLevelIndex == 0) {
516 sLong OldVolume = GlobalRainLiquid->GetVolume();
517 sLong NewVolume = Max(sLong(sin((Tick+GlobalRainTimeModifier)*0.0003)*300-150), 0);
518 if (NewVolume && !OldVolume) CurrentLevel->EnableGlobalRain();
519 else if(!NewVolume && OldVolume) CurrentLevel->DisableGlobalRain();
520 GlobalRainLiquid->SetVolumeNoSignals(NewVolume);
523 item *Item;
524 if (!RAND_N(2)) Item = wand::Spawn(1 + RAND_N(12));
525 else if(!RAND_N(2)) {
526 Item = beartrap::Spawn();
527 Item->SetIsActive(true);
528 Item->SetTeam(MONSTER_TEAM);
529 } else if(!RAND_N(2)) {
530 Item = mine::Spawn();
531 Item->SetIsActive(true);
532 Item->SetTeam(MONSTER_TEAM);
533 } else Item = holybanana::Spawn();
534 CurrentLevel->GetLSquare(CurrentLevel->GetRandomSquare())->AddItem(Item);
537 if(!RAND_N(10)) {
538 character *Char = protosystem::CreateMonster(0, 1000000);
539 Char->ChangeTeam(GetTeam(RAND() % Teams));
540 Char->PutTo(CurrentLevel->GetRandomSquare(Char));
543 if (!RAND_N(5)) {
544 character *Char;
545 if (!RAND_N(5)) Char = spider::Spawn(GIANT);
546 else if (!RAND_N(5)) Char = darkmage::Spawn(1 + RAND_N(4));
547 else if (!RAND_N(5)) Char = necromancer::Spawn(1 + RAND_N(2));
548 else if (!RAND_N(5)) Char = chameleon::Spawn();
549 else if (!RAND_N(5)) Char = kamikazedwarf::Spawn(1 + RAND_N(GODS));
550 else if (!RAND_N(5)) Char = mommo::Spawn(1 + RAND_N(2));
551 else if (!RAND_N(3)) Char = bunny::Spawn(RAND_2 ? ADULT_MALE : ADULT_FEMALE);
552 else if (!RAND_N(3)) Char = eddy::Spawn();
553 else if (!RAND_N(3)) Char = magicmushroom::Spawn();
554 else if (!RAND_N(5)) Char = mushroom::Spawn();
555 else if (!RAND_N(3)) Char = blinkdog::Spawn();
556 else if (!RAND_N(5)) Char = tourist::Spawn(1 + RAND_N(3));
557 else if (!RAND_N(5)) Char = hattifattener::Spawn();
558 else if (!RAND_N(5)) Char = genetrixvesana::Spawn();
559 else if (!RAND_N(5)) Char = skunk::Spawn();
560 else if (!RAND_N(5)) Char = ennerbeast::Spawn();
561 else if (!RAND_N(5)) Char = werewolfhuman::Spawn();
562 else if (!RAND_N(5)) Char = unicorn::Spawn(1 + RAND_N(3));
563 else if (!RAND_N(5)) Char = floatingeye::Spawn();
564 else if (!RAND_N(5)) Char = zombie::Spawn();
565 else if (!RAND_N(5)) Char = magpie::Spawn();
566 else if (!RAND_N(5)) Char = elpuri::Spawn();
567 else if (!RAND_N(5)) Char = vladimir::Spawn();
568 else if (!RAND_N(5)) Char = billswill::Spawn();
569 else if (!RAND_N(5)) Char = ghost::Spawn();
570 else if (!RAND_N(5)) Char = dolphin::Spawn();
571 else if (!RAND_N(5)) Char = cossack::Spawn();
572 else Char = invisiblestalker::Spawn();
573 Char->SetTeam(GetTeam(RAND() % Teams));
574 Char->PutTo(CurrentLevel->GetRandomSquare(Char));
580 try {
581 pool::Be();
582 pool::BurnHell();
583 IncreaseTick();
584 ApplyDivineTick();
585 } catch (quitrequest) {
586 break;
587 } catch (areachangerequest) {
593 void game::InitLuxTable () {
594 if (!LuxTable) {
595 Alloc3D(LuxTable, 256, 33, 33);
596 for (int c = 0; c < 0x100; ++c)
597 for (int x = 0; x < 33; ++x)
598 for (int y = 0; y < 33; ++y) {
599 int X = x-16, Y = y-16;
600 LuxTable[c][x][y] = int(c/(double(X*X+Y*Y)/128+1));
602 atexit(DeInitLuxTable);
607 void game::DeInitLuxTable () {
608 delete [] LuxTable;
609 LuxTable = 0;
613 void game::UpdateCameraX () {
614 UpdateCameraX(Player->GetPos().X);
618 void game::UpdateCameraY () {
619 UpdateCameraY(Player->GetPos().Y);
623 void game::UpdateCameraX (int X) {
624 UpdateCameraCoordinate(Camera.X, X, GetCurrentArea()->GetXSize(), GetScreenXSize());
628 void game::UpdateCameraY (int Y) {
629 UpdateCameraCoordinate(Camera.Y, Y, GetCurrentArea()->GetYSize(), GetScreenYSize());
633 void game::UpdateCameraCoordinate (int &Coordinate, int Center, int Size, int ScreenSize) {
634 int OldCoordinate = Coordinate;
635 if (Size < ScreenSize) Coordinate = (Size-ScreenSize)>>1;
636 else if(Center < ScreenSize>>1) Coordinate = 0;
637 else if(Center > Size-(ScreenSize>>1)) Coordinate = Size-ScreenSize;
638 else Coordinate = Center-(ScreenSize>>1);
639 if (Coordinate != OldCoordinate) GetCurrentArea()->SendNewDrawRequest();
643 cchar *game::Insult () {
644 static const char *insults[19] = {
645 "moron",
646 "silly",
647 "idiot",
648 "airhead",
649 "jerk",
650 "dork",
651 "Mr. Mole",
652 "navastater",
653 "potatoes-for-eyes",
654 "lamer",
655 "mommo-for-brains",
656 "pinhead",
657 "stupid-headed person",
658 "software abuser",
659 "loser",
660 "peaballs",
661 "person-with-problems",
662 "unimportant user",
663 "hugger-mugger"
665 int n = RAND_N(18);
666 if (n < 0 || n > 18) n = 18;
667 return insults[n];
671 /* DefaultAnswer = REQUIRES_ANSWER the question requires an answer */
672 truth game::TruthQuestion (cfestring &String, int DefaultAnswer, int OtherKeyForTrue) {
673 if (DefaultAnswer == NO) DefaultAnswer = 'n';
674 else if (DefaultAnswer == YES) DefaultAnswer = 'y';
675 else if (DefaultAnswer != REQUIRES_ANSWER) ABORT("Illegal TruthQuestion DefaultAnswer send!");
676 int FromKeyQuestion = KeyQuestion(String, DefaultAnswer, 9, 'y', 'Y', 'n', 'N', 't', 'T', 'o', 'O', OtherKeyForTrue);
677 return
678 FromKeyQuestion == 'y' || FromKeyQuestion == 'Y' ||
679 FromKeyQuestion == 't' || FromKeyQuestion == 'T' ||
680 FromKeyQuestion == OtherKeyForTrue;
684 void game::DrawEverything () {
685 DrawEverythingNoBlit();
686 graphics::BlitDBToScreen();
690 truth game::OnScreen (v2 Pos) {
691 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();
695 void game::DrawEverythingNoBlit (truth AnimationDraw) {
696 if (LOSUpdateRequested && Player->IsEnabled()) {
697 if (!IsInWilderness()) GetCurrentLevel()->UpdateLOS(); else GetWorldMap()->UpdateLOS();
700 if (OnScreen(CursorPos)) {
701 if (!IsInWilderness() || CurrentWSquareMap[CursorPos.X][CursorPos.Y]->GetLastSeen() || GetSeeWholeMapCheatMode())
702 CurrentArea->GetSquare(CursorPos)->SendStrongNewDrawRequest();
703 else
704 DOUBLE_BUFFER->Fill(CalculateScreenCoordinates(CursorPos), TILE_V2, 0);
707 for (unsigned int c = 0; c < SpecialCursorPos.size(); ++c) {
708 if (OnScreen(SpecialCursorPos[c])) CurrentArea->GetSquare(SpecialCursorPos[c])->SendStrongNewDrawRequest();
711 globalwindowhandler::UpdateTick();
712 GetCurrentArea()->Draw(AnimationDraw);
713 Player->DrawPanel(AnimationDraw);
715 if (!AnimationDraw) msgsystem::Draw();
717 if (OnScreen(CursorPos)) {
718 v2 ScreenCoord = CalculateScreenCoordinates(CursorPos);
719 blitdata B = {
720 DOUBLE_BUFFER,
721 { 0, 0 },
722 { ScreenCoord.X, ScreenCoord.Y },
723 { TILE_SIZE, TILE_SIZE },
724 { 0 },
725 TRANSPARENT_COLOR,
726 ALLOW_ANIMATE|ALLOW_ALPHA
729 if (!IsInWilderness() && !GetSeeWholeMapCheatMode()) {
730 lsquare *Square = CurrentLSquareMap[CursorPos.X][CursorPos.Y];
731 if (Square->GetLastSeen() != GetLOSTick()) Square->DrawMemorized(B);
734 if (DoZoom()) {
735 B.Src = B.Dest;
736 B.Dest.X = RES.X - 96;
737 B.Dest.Y = RES.Y - 96;
738 B.Stretch = 5;
739 DOUBLE_BUFFER->StretchBlit(B);
742 igraph::DrawCursor(ScreenCoord, CursorData);
745 if (Player->IsEnabled()) {
746 if (Player->IsSmall()) {
747 v2 Pos = Player->GetPos();
748 if (OnScreen(Pos)) {
749 v2 ScreenCoord = CalculateScreenCoordinates(Pos);
750 igraph::DrawCursor(ScreenCoord, Player->GetCursorData());
752 } else {
753 for (int f = 0; f < Player->GetSquaresUnder(); ++f) {
754 v2 Pos = Player->GetPos(f);
755 if (OnScreen(Pos)) {
756 v2 ScreenCoord = CalculateScreenCoordinates(Pos);
757 igraph::DrawCursor(ScreenCoord, Player->GetCursorData()|CURSOR_BIG, f);
763 for (unsigned int c = 0; c < SpecialCursorPos.size(); ++c) {
764 if (OnScreen(SpecialCursorPos[c])) {
765 v2 ScreenCoord = CalculateScreenCoordinates(SpecialCursorPos[c]);
766 igraph::DrawCursor(ScreenCoord, SpecialCursorData[c]);
767 GetCurrentArea()->GetSquare(SpecialCursorPos[c])->SendStrongNewDrawRequest();
773 truth game::Save (cfestring &SaveName) {
774 if (!GetCurrentArea()->GetSquare(Player->GetPos())->GetCharacter()) {
775 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);
776 return false;
778 DrawEverythingNoBlit();
779 #if defined(SGAME_SHOTS_IPU) || !defined(HAVE_IMLIB2)
780 DOUBLE_BUFFER->SaveScaledIPU(SaveName+".ipu", 0.8); //640; 320
781 #else
782 DOUBLE_BUFFER->SaveScaledPNG(SaveName+".png", 0.8); //640; 320
783 #endif
784 outputfile SaveFile(SaveName+".sav", ivanconfig::GetUseMaximumCompression());
785 SaveFile << int(SAVE_FILE_VERSION);
786 SaveFile << GameScript << CurrentDungeonIndex << CurrentLevelIndex << Camera;
787 SaveFile << WizardMode << SeeWholeMapCheatMode << GoThroughWallsCheat;
788 SaveFile << Tick << Turn << InWilderness << NextCharacterID << NextItemID << NextTrapID << NecroCounter;
789 SaveFile << SumoWrestling << PlayerSumoChampion << GlobalRainTimeModifier;
790 SaveFile << PlayerSolicitusChampion;
791 sLong Seed = RAND();
792 femath::SetSeed(Seed);
793 SaveFile << Seed;
794 SaveFile << AveragePlayerArmStrengthExperience;
795 SaveFile << AveragePlayerLegStrengthExperience;
796 SaveFile << AveragePlayerDexterityExperience;
797 SaveFile << AveragePlayerAgilityExperience;
798 SaveFile << Teams << Dungeons << StoryState << PlayerRunning;
799 SaveFile << MondedrPass << RingOfThieves << Masamune << Muramasa << LoricatusHammer << Liberator;
800 SaveFile << OmmelBloodMission << RegiiTalkState;
801 SaveFile << PlayerMassacreMap << PetMassacreMap << MiscMassacreMap;
802 SaveFile << PlayerMassacreAmount << PetMassacreAmount << MiscMassacreAmount;
803 SaveArray(SaveFile, EquipmentMemory, MAX_EQUIPMENT_SLOTS);
804 int c;
805 for (c = 0; c < ATTRIBUTES; ++c) SaveFile << OldAttribute[c] << NewAttribute[c] << LastAttributeChangeTick[c];
806 for (c = 1; c < Dungeons; ++c) SaveFile << Dungeon[c];
807 for (c = 1; c <= GODS; ++c) SaveFile << God[c];
808 for (c = 0; c < Teams; ++c) SaveFile << Team[c];
809 if (InWilderness) {
810 SaveWorldMap(SaveName, false);
811 } else {
812 GetCurrentDungeon()->SaveLevel(SaveName, CurrentLevelIndex, false);
814 SaveFile << Player->GetPos() << PlayerName;
815 msgsystem::Save(SaveFile);
816 SaveFile << DangerMap << NextDangerIDType << NextDangerIDConfigIndex;
817 SaveFile << DefaultPolymorphTo << DefaultSummonMonster;
818 SaveFile << DefaultWish << DefaultChangeMaterial << DefaultDetectMaterial;
819 SaveFile << GetTimeSpent();
820 /* or in more readable format: time() - LastLoad + TimeAtLastLoad */
821 SaveFile << PlayerHasReceivedAllGodsKnownBonus;
822 protosystem::SaveCharacterDataBaseFlags(SaveFile);
823 return true;
827 int game::Load (cfestring &SaveName) {
828 inputfile SaveFile(SaveName+".sav", 0, false);
829 if (!SaveFile.IsOpen()) return NEW_GAME;
830 int Version;
831 SaveFile >> Version;
832 if (Version != SAVE_FILE_VERSION) {
833 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)) {
834 return NEW_GAME;
835 } else {
836 return BACK;
839 SaveFile >> GameScript >> CurrentDungeonIndex >> CurrentLevelIndex >> Camera;
840 SaveFile >> WizardMode >> SeeWholeMapCheatMode >> GoThroughWallsCheat;
841 SaveFile >> Tick >> Turn >> InWilderness >> NextCharacterID >> NextItemID >> NextTrapID >> NecroCounter;
842 SaveFile >> SumoWrestling >> PlayerSumoChampion >> GlobalRainTimeModifier;
843 SaveFile >> PlayerSolicitusChampion;
844 femath::SetSeed(ReadType<sLong>(SaveFile));
845 SaveFile >> AveragePlayerArmStrengthExperience;
846 SaveFile >> AveragePlayerLegStrengthExperience;
847 SaveFile >> AveragePlayerDexterityExperience;
848 SaveFile >> AveragePlayerAgilityExperience;
849 SaveFile >> Teams >> Dungeons >> StoryState >> PlayerRunning;
850 SaveFile >> MondedrPass >> RingOfThieves >> Masamune >> Muramasa >> LoricatusHammer >> Liberator;
851 SaveFile >> OmmelBloodMission >> RegiiTalkState;
852 SaveFile >> PlayerMassacreMap >> PetMassacreMap >> MiscMassacreMap;
853 SaveFile >> PlayerMassacreAmount >> PetMassacreAmount >> MiscMassacreAmount;
854 LoadArray(SaveFile, EquipmentMemory, MAX_EQUIPMENT_SLOTS);
855 for (int c = 0; c < ATTRIBUTES; ++c) SaveFile >> OldAttribute[c] >> NewAttribute[c] >> LastAttributeChangeTick[c];
856 Dungeon = new dungeon*[Dungeons];
857 Dungeon[0] = 0;
858 for (int c = 1; c < Dungeons; ++c) SaveFile >> Dungeon[c];
859 God = new god*[GODS+1];
860 God[0] = 0;
861 for (int c = 1; c <= GODS; ++c) SaveFile >> God[c];
862 Team = new team*[Teams];
863 for (int c = 0; c < Teams; ++c) SaveFile >> Team[c];
864 if (InWilderness) {
865 SetCurrentArea(LoadWorldMap(SaveName));
866 CurrentWSquareMap = WorldMap->GetMap();
867 igraph::CreateBackGround(GRAY_FRACTAL);
868 } else {
869 SetCurrentArea(CurrentLevel = GetCurrentDungeon()->LoadLevel(SaveName, CurrentLevelIndex));
870 CurrentLSquareMap = CurrentLevel->GetMap();
871 igraph::CreateBackGround(*CurrentLevel->GetLevelScript()->GetBackGroundType());
873 v2 Pos;
874 SaveFile >> Pos >> PlayerName;
875 SetPlayer(GetCurrentArea()->GetSquare(Pos)->GetCharacter());
876 if (!PLAYER) {
877 DeInit();
878 if (!iosystem::Menu(0, v2(RES.X >> 1, RES.Y >> 1), CONST_S("Sorry, this save is broken due to bug in I.V.A.N.\rStart new game?\r"), CONST_S("Yes\rNo\r"), LIGHT_GRAY)) {
879 return NEW_GAME;
880 } else {
881 return BACK;
884 msgsystem::Load(SaveFile);
885 SaveFile >> DangerMap >> NextDangerIDType >> NextDangerIDConfigIndex;
886 SaveFile >> DefaultPolymorphTo >> DefaultSummonMonster;
887 SaveFile >> DefaultWish >> DefaultChangeMaterial >> DefaultDetectMaterial;
888 SaveFile >> TimePlayedBeforeLastLoad;
889 SaveFile >> PlayerHasReceivedAllGodsKnownBonus;
890 LastLoad = time(0);
891 protosystem::LoadCharacterDataBaseFlags(SaveFile);
892 return LOADED;
896 festring game::SaveName (cfestring &Base) {
897 festring SaveName = GetSaveDir();
898 if (!Base.GetSize()) SaveName << PlayerName; else SaveName << Base;
899 for (festring::sizetype c = 0; c < SaveName.GetSize(); ++c) if (SaveName[c] == ' ') SaveName[c] = '_';
900 return SaveName;
904 int game::GetMoveCommandKeyBetweenPoints (v2 A, v2 B) {
905 for (int c = 0; c < EXTENDED_DIRECTION_COMMAND_KEYS; ++c) {
906 if ((A + GetMoveVector(c)) == B) return GetMoveCommandKey(c);
908 return DIR_ERROR;
912 void game::ApplyDivineTick () {
913 for (int c = 1; c <= GODS; ++c) GetGod(c)->ApplyDivineTick();
917 void game::ApplyDivineAlignmentBonuses (god *CompareTarget, int Multiplier, truth Good) {
918 for (int c = 1; c <= GODS; ++c) if (GetGod(c) != CompareTarget) GetGod(c)->AdjustRelation(CompareTarget, Multiplier, Good);
922 v2 game::GetDirectionVectorForKey (int Key) {
923 if (Key == KEY_NUMPAD_5 || Key == '.') return ZERO_V2; /* k8: '.' */
924 for (int c = 0; c < EXTENDED_DIRECTION_COMMAND_KEYS; ++c) if (Key == GetMoveCommandKey(c)) return GetMoveVector(c);
925 return ERROR_V2;
929 double game::GetMinDifficulty () {
930 double Base = CurrentLevel->GetDifficulty()*0.2;
931 sLong MultiplierExponent = 0;
932 ivantime Time;
933 GetTime(Time);
934 int Modifier = Time.Day-DANGER_PLUS_DAY_MIN;
935 if (Modifier > 0) Base += DANGER_PLUS_MULTIPLIER * Modifier;
936 for (;;) {
937 int Dice = RAND()%25;
938 if (Dice < 5 && MultiplierExponent > -3) {
939 Base /= 3;
940 --MultiplierExponent;
941 continue;
943 if (Dice >= 20 && MultiplierExponent < 3) {
944 Base *= 3;
945 ++MultiplierExponent;
946 continue;
948 return Base;
953 void game::ShowLevelMessage () {
954 if (CurrentLevel->GetLevelMessage().GetSize()) ADD_MESSAGE("%s", CurrentLevel->GetLevelMessage().CStr());
955 CurrentLevel->SetLevelMessage("");
959 int game::DirectionQuestion (cfestring &Topic, truth RequireAnswer, truth AcceptYourself) {
960 for (;;) {
961 int Key = AskForKeyPress(Topic);
962 if (AcceptYourself && (Key == '.' || Key == KEY_NUMPAD_5)) return YOURSELF; //k8
963 for (int c = 0; c < DIRECTION_COMMAND_KEYS; ++c) if (Key == GetMoveCommandKey(c)) return c;
964 if (!RequireAnswer) return DIR_ERROR;
969 void game::RemoveSaves (truth RealSavesAlso) {
970 if (RealSavesAlso) {
971 remove(festring(SaveName()+".sav").CStr());
972 remove(festring(SaveName()+".wm").CStr());
973 remove(festring(SaveName()+".png").CStr());
974 remove(festring(SaveName()+".ipu").CStr());
976 remove(festring(AutoSaveFileName+".sav").CStr());
977 remove(festring(AutoSaveFileName+".wm").CStr());
978 remove(festring(AutoSaveFileName+".png").CStr());
979 remove(festring(AutoSaveFileName+".ipu").CStr());
980 festring File;
981 for (int i = 1; i < Dungeons; ++i) {
982 for (int c = 0; c < GetDungeon(i)->GetLevels(); ++c) {
983 /* This looks very odd. And it is very odd.
984 * Indeed, gcc is very odd to not compile this correctly with -O3
985 * if it is written in a less odd way. */
986 File = SaveName()+'.'+i;
987 File << c;
988 if (RealSavesAlso) remove(File.CStr());
989 File = AutoSaveFileName+'.'+i;
990 File << c;
991 remove(File.CStr());
997 void game::SetPlayer (character *NP) {
998 Player = NP;
999 if (Player) Player->AddFlags(C_PLAYER);
1003 void game::InitDungeons () {
1004 Dungeons = *GetGameScript()->GetDungeons()+1;
1005 //fprintf(stderr, "dungeon count: %d\n", Dungeons);
1006 Dungeon = new dungeon *[Dungeons];
1007 Dungeon[0] = 0;
1008 for (int c = 1; c < Dungeons; ++c) {
1009 Dungeon[c] = new dungeon(c);
1010 Dungeon[c]->SetIndex(c);
1015 void game::DoEvilDeed (int Amount) {
1016 if (!Amount) return;
1017 for (int c = 1; c <= GODS; ++c) {
1018 int Change = Amount-Amount*GetGod(c)->GetAlignment()/5;
1019 if (!IsInWilderness() && Player->GetLSquareUnder()->GetDivineMaster() == c) {
1020 if (GetGod(c)->GetRelation()-(Change << 1) < -750) {
1021 if (GetGod(c)->GetRelation() > -750) GetGod(c)->SetRelation(-750);
1022 } else if (GetGod(c)->GetRelation()-(Change << 1) > 750) {
1023 if (GetGod(c)->GetRelation() < 750) GetGod(c)->SetRelation(750);
1024 } else GetGod(c)->SetRelation(GetGod(c)->GetRelation()-(Change << 1));
1025 } else {
1026 if(GetGod(c)->GetRelation()-Change < -500) {
1027 if (GetGod(c)->GetRelation() > -500) GetGod(c)->SetRelation(-500);
1028 } else if (GetGod(c)->GetRelation()-Change > 500) {
1029 if (GetGod(c)->GetRelation() < 500) GetGod(c)->SetRelation(500);
1030 } else GetGod(c)->SetRelation(GetGod(c)->GetRelation() - Change);
1036 void game::SaveWorldMap (cfestring &SaveName, truth DeleteAfterwards) {
1037 outputfile SaveFile(SaveName+".wm", ivanconfig::GetUseMaximumCompression());
1038 SaveFile << WorldMap;
1039 if (DeleteAfterwards) {
1040 delete WorldMap;
1041 WorldMap = 0;
1046 worldmap *game::LoadWorldMap (cfestring &SaveName) {
1047 inputfile SaveFile(SaveName+".wm");
1048 SaveFile >> WorldMap;
1049 return WorldMap;
1053 void game::Hostility (team *Attacker, team *Defender) {
1054 for (int c = 0; c < Teams; ++c) {
1055 if (GetTeam(c) != Attacker && GetTeam(c) != Defender &&
1056 GetTeam(c)->GetRelation(Defender) == FRIEND &&
1057 c != NEW_ATTNAM_TEAM && c != TOURIST_GUIDE_TEAM) // gum solution
1058 GetTeam(c)->SetRelation(Attacker, HOSTILE);
1063 void game::CreateTeams () {
1064 Teams = *GetGameScript()->GetTeams();
1065 //fprintf(stderr, "team count: %d\n", Teams);
1066 Team = new team*[Teams];
1067 int c;
1068 for (c = 0; c < Teams; ++c) {
1069 Team[c] = new team(c);
1070 for (int i = 0; i < c; ++i) Team[i]->SetRelation(Team[c], UNCARING);
1072 for (c = 0; c < Teams; ++c) if (c != 1) Team[1]->SetRelation(Team[c], HOSTILE);
1073 const std::list<std::pair<int, teamscript> >& TeamScript = GetGameScript()->GetTeam();
1074 for (std::list<std::pair<int, teamscript> >::const_iterator i = TeamScript.begin(); i != TeamScript.end(); ++i) {
1075 for (uInt c = 0; c < i->second.GetRelation().size(); ++c) {
1076 GetTeam(i->second.GetRelation()[c].first)->SetRelation(GetTeam(i->first), i->second.GetRelation()[c].second);
1078 cint *KillEvilness = i->second.GetKillEvilness();
1079 if (KillEvilness) GetTeam(i->first)->SetKillEvilness(*KillEvilness);
1084 /* v2 Pos should be removed from xxxQuestion()s? */
1085 festring game::StringQuestion (cfestring &Topic, col16 Color, festring::sizetype MinLetters, festring::sizetype MaxLetters, truth AllowExit, stringkeyhandler KeyHandler) {
1086 DrawEverythingNoBlit();
1087 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23)); // pos may be incorrect!
1088 festring Return;
1089 iosystem::StringQuestion(Return, Topic, v2(16, 6), Color, MinLetters, MaxLetters, false, AllowExit, KeyHandler);
1090 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1091 return Return;
1095 sLong game::NumberQuestion (cfestring &Topic, col16 Color, truth ReturnZeroOnEsc) {
1096 DrawEverythingNoBlit();
1097 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1098 sLong Return = iosystem::NumberQuestion(Topic, v2(16, 6), Color, false, ReturnZeroOnEsc);
1099 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1100 return Return;
1104 sLong game::ScrollBarQuestion (cfestring &Topic, sLong BeginValue, sLong Step, sLong Min, sLong Max, sLong AbortValue, col16 TopicColor, col16 Color1, col16 Color2, void (*Handler)(sLong)) {
1105 DrawEverythingNoBlit();
1106 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1107 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);
1108 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1109 return Return;
1113 feuLong game::IncreaseLOSTick () {
1114 if (LOSTick != 0xFE) return LOSTick += 2;
1115 CurrentLevel->InitLastSeen();
1116 return LOSTick = 4;
1120 void game::UpdateCamera () {
1121 UpdateCameraX();
1122 UpdateCameraY();
1126 truth game::HandleQuitMessage () {
1127 if (IsRunning()) {
1128 if (IsInGetCommand()) {
1129 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)) {
1130 case 0:
1131 Save();
1132 RemoveSaves(false);
1133 break;
1134 case 2:
1135 GetCurrentArea()->SendNewDrawRequest();
1136 DrawEverything();
1137 return false;
1138 default:
1139 festring Msg = CONST_S("cowardly quit the game");
1140 Player->AddScoreEntry(Msg, 0.75);
1141 End(Msg, true, false);
1142 break;
1144 } 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)) {
1145 RemoveSaves();
1146 } else {
1147 GetCurrentArea()->SendNewDrawRequest();
1148 DrawEverything();
1149 return false;
1152 return true;
1156 int game::GetDirectionForVector (v2 Vector) {
1157 for (int c = 0; c < DIRECTION_COMMAND_KEYS; ++c) if (Vector == GetMoveVector(c)) return c;
1158 return DIR_ERROR;
1162 cchar *game::GetVerbalPlayerAlignment () {
1163 sLong Sum = 0;
1164 for (int c = 1; c <= GODS; ++c) {
1165 if (GetGod(c)->GetRelation() > 0) Sum += GetGod(c)->GetRelation() * (5 - GetGod(c)->GetAlignment());
1167 if (Sum > 15000) return "extremely lawful";
1168 if (Sum > 10000) return "very lawful";
1169 if (Sum > 5000) return "lawful";
1170 if (Sum > 1000) return "mildly lawful";
1171 if (Sum > -1000) return "neutral";
1172 if (Sum > -5000) return "mildly chaotic";
1173 if (Sum > -10000) return "chaotic";
1174 if (Sum > -15000) return "very chaotic";
1175 return "extremely chaotic";
1179 void game::CreateGods () {
1180 God = new god*[GODS+1];
1181 God[0] = 0;
1182 for (int c = 1; c < protocontainer<god>::GetSize(); ++c) God[c] = protocontainer<god>::GetProto(c)->Spawn();
1186 void game::BusyAnimation () {
1187 BusyAnimation(DOUBLE_BUFFER, false);
1191 void game::BusyAnimation (bitmap *Buffer, truth ForceDraw) {
1192 static clock_t LastTime = 0;
1193 static int Frame = 0;
1194 static blitdata B1 = {
1196 { 0, 0 },
1197 { 0, 0 },
1198 { RES.X, RES.Y },
1199 { 0 },
1203 static blitdata B2 = {
1205 { 0, 0 },
1206 { (RES.X >> 1) - 100, (RES.Y << 1) / 3 - 100 },
1207 { 200, 200 },
1208 { 0 },
1212 if (ForceDraw || clock()-LastTime > CLOCKS_PER_SEC/25) {
1213 B2.Bitmap = Buffer;
1214 B2.Dest.X = (RES.X>>1)-100+EnterTextDisplacement.X;
1215 B2.Dest.Y = (RES.Y<<1)/3-100+EnterTextDisplacement.Y;
1216 if (EnterImage) {
1217 B1.Bitmap = Buffer;
1218 EnterImage->NormalMaskedBlit(B1);
1220 BusyAnimationCache[Frame]->NormalBlit(B2);
1221 if (Buffer == DOUBLE_BUFFER) graphics::BlitDBToScreen();
1222 if (++Frame == 32) Frame = 0;
1223 LastTime = clock();
1228 void game::CreateBusyAnimationCache () {
1229 bitmap Elpuri(TILE_V2, TRANSPARENT_COLOR);
1230 Elpuri.ActivateFastFlag();
1231 packcol16 Color = MakeRGB16(60, 60, 60);
1232 igraph::GetCharacterRawGraphic()->MaskedBlit(&Elpuri, v2(64, 0), ZERO_V2, TILE_V2, &Color);
1233 bitmap Circle(v2(200, 200), TRANSPARENT_COLOR);
1234 Circle.ActivateFastFlag();
1235 for (int x = 0; x < 4; ++x) Circle.DrawPolygon(100, 100, 95+x, 50, MakeRGB16(255-12*x, 0, 0));
1236 blitdata B1 = {
1238 { 0, 0 },
1239 { 92, 92 },
1240 { TILE_SIZE, TILE_SIZE },
1241 { 0 },
1242 TRANSPARENT_COLOR,
1245 blitdata B2 = {
1247 { 0, 0 },
1248 { 0, 0 },
1249 { 200, 200 },
1250 { 0 },
1251 TRANSPARENT_COLOR,
1254 for (int c = 0; c < 32; ++c) {
1255 B1.Bitmap = B2.Bitmap = BusyAnimationCache[c] = new bitmap(v2(200, 200), 0);
1256 B1.Bitmap->ActivateFastFlag();
1257 Elpuri.NormalMaskedBlit(B1);
1258 double Rotation = 0.3+c*FPI/80;
1259 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);
1260 Circle.NormalMaskedBlit(B2);
1265 int game::AskForKeyPress (cfestring &Topic) {
1266 DrawEverythingNoBlit();
1267 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Topic.CapitalizeCopy().CStr());
1268 graphics::BlitDBToScreen();
1269 int Key = GET_KEY();
1270 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1271 return Key;
1275 void game::AskForEscPress (cfestring &Topic) {
1276 DrawEverythingNoBlit();
1277 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), RED/*WHITE*/, "%s [press ESC]", Topic.CapitalizeCopy().CStr());
1278 graphics::BlitDBToScreen();
1279 int Key;
1280 do {
1281 Key = GET_KEY();
1282 } while (Key != KEY_ESC);
1283 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1287 /* Handler is called when the key has been identified as a movement key
1288 * KeyHandler is called when the key has NOT been identified as a movement key
1289 * Both can be deactivated by passing 0 as parameter */
1290 v2 game::PositionQuestion (cfestring &Topic, v2 CursorPos, void (*Handler)(v2), positionkeyhandler KeyHandler, truth Zoom) {
1291 int Key = 0;
1292 SetDoZoom(Zoom);
1293 v2 Return;
1294 CursorData = RED_CURSOR;
1295 if (Handler) Handler(CursorPos);
1296 for (;;) {
1297 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1298 if (!Square->HasBeenSeen() &&
1299 (!Square->GetCharacter() || !Square->GetCharacter()->CanBeSeenByPlayer()) &&
1300 !GetSeeWholeMapCheatMode()) DOUBLE_BUFFER->Fill(CalculateScreenCoordinates(CursorPos), TILE_V2, BLACK);
1301 else GetCurrentArea()->GetSquare(CursorPos)->SendStrongNewDrawRequest();
1303 if (Key == ' ' || Key == '.' || Key == KEY_NUMPAD_5) { Return = CursorPos; break; }
1304 if (Key == KEY_ESC) { Return = ERROR_V2; break; }
1306 v2 DirectionVector = GetDirectionVectorForKey(Key);
1307 if (DirectionVector != ERROR_V2) {
1308 CursorPos += DirectionVector;
1309 if (CursorPos.X > GetCurrentArea()->GetXSize()-1) CursorPos.X = 0;
1310 if (CursorPos.X < 0) CursorPos.X = GetCurrentArea()->GetXSize()-1;
1311 if (CursorPos.Y > GetCurrentArea()->GetYSize()-1) CursorPos.Y = 0;
1312 if (CursorPos.Y < 0) CursorPos.Y = GetCurrentArea()->GetYSize()-1;
1313 if (Handler) Handler(CursorPos);
1314 } else if (KeyHandler) {
1315 CursorPos = KeyHandler(CursorPos, Key);
1316 if (CursorPos == ERROR_V2 || CursorPos == ABORT_V2) {
1317 Return = CursorPos;
1318 break;
1322 if (ivanconfig::GetAutoCenterMapOnLook()) {
1323 UpdateCameraX(CursorPos.X);
1324 UpdateCameraY(CursorPos.Y);
1325 } else {
1326 if (CursorPos.X < GetCamera().X+3 || CursorPos.X >= GetCamera().X+GetScreenXSize()-3) UpdateCameraX(CursorPos.X);
1327 if (CursorPos.Y < GetCamera().Y+3 || CursorPos.Y >= GetCamera().Y+GetScreenYSize()-3) UpdateCameraY(CursorPos.Y);
1330 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Topic.CStr());
1331 SetCursorPos(CursorPos);
1332 DrawEverything();
1333 Key = GET_KEY();
1336 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1337 igraph::BlitBackGround(v2(RES.X-96, RES.Y-96), v2(80, 80));
1338 SetDoZoom(false);
1339 SetCursorPos(v2(-1, -1));
1340 return Return;
1344 void game::LookHandler (v2 CursorPos) {
1345 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1346 festring OldMemory;
1348 if (GetSeeWholeMapCheatMode()) {
1349 OldMemory = Square->GetMemorizedDescription();
1350 if (IsInWilderness()) GetWorldMap()->GetWSquare(CursorPos)->UpdateMemorizedDescription(true);
1351 else GetCurrentLevel()->GetLSquare(CursorPos)->UpdateMemorizedDescription(true);
1354 festring Msg;
1355 if (Square->HasBeenSeen() || GetSeeWholeMapCheatMode()) {
1356 if (!IsInWilderness() && !Square->CanBeSeenByPlayer() && GetCurrentLevel()->GetLSquare(CursorPos)->CanBeFeltByPlayer())
1357 Msg = CONST_S("You feel here ");
1358 else if (Square->CanBeSeenByPlayer(true) || GetSeeWholeMapCheatMode())
1359 Msg = CONST_S("You see here ");
1360 else
1361 Msg = CONST_S("You remember here ");
1362 Msg << Square->GetMemorizedDescription() << '.';
1363 if (!IsInWilderness() && (Square->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) {
1364 lsquare *LSquare = GetCurrentLevel()->GetLSquare(CursorPos);
1365 LSquare->DisplaySmokeInfo(Msg);
1366 if (LSquare->HasEngravings() && LSquare->IsTransparent()) {
1367 if (LSquare->EngravingsCanBeReadByPlayer() || GetSeeWholeMapCheatMode()) LSquare->DisplayEngravedInfo(Msg);
1368 else Msg << " Something has been engraved here.";
1371 } else Msg = CONST_S("You have never been here.");
1372 character *Character = Square->GetCharacter();
1373 if (Character && (Character->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) Character->DisplayInfo(Msg);
1374 if (!(RAND()%10000) && (Square->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) Msg << " You see here a frog eating a magnolia.";
1375 ADD_MESSAGE("%s", Msg.CStr());
1376 if (GetSeeWholeMapCheatMode()) Square->SetMemorizedDescription(OldMemory);
1380 truth game::AnimationController () {
1381 DrawEverythingNoBlit(true);
1382 return true;
1386 void game::LoadGlobalValueMap (inputfile &fl) {
1387 festring word;
1388 fl.setGetVarCB(game::ldrGetVar);
1389 for (fl.ReadWord(word, false); !fl.Eof(); fl.ReadWord(word, false)) {
1390 if (word == "Include") {
1391 word = fl.ReadWord();
1392 if (fl.ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1393 //fprintf(stderr, "loading: %s\n", word.CStr());
1394 inputfile incf(game::GetGameDir()+"Script/"+word, &game::GetGlobalValueMap());
1395 LoadGlobalValueMap(incf);
1396 continue;
1398 if (word == "Message") {
1399 word = fl.ReadWord();
1400 if (fl.ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1401 fprintf(stderr, "MESSAGE: %s\n", word.CStr());
1402 continue;
1404 if (word != "#") ABORT("Illegal datafile define in file %s on line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1405 fl.ReadWord(word, true);
1406 if (word == "enum" || word == "bitenum") {
1407 truth isBit = word == "bitenum";
1408 sLong idx = 0;
1409 if (fl.ReadWord() != "{") ABORT("'{' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1410 festring idName;
1411 truth done = false;
1412 while (!done) {
1413 fl.ReadWord(word, true);
1414 if (word == "}") break;
1415 if (word == "=") {
1416 idName.Empty();
1417 } else {
1418 idName = word;
1419 fl.ReadWord(word, true);
1421 if (word == "=") {
1422 // set current index
1423 idx = fl.ReadNumber();
1424 } else {
1425 if (word != "," && word != ";" && word != "}") ABORT("',' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1426 if (word == "}") done = true;
1428 if (idName.GetSize() > 0) {
1429 sLong i = idx;
1430 if (isBit) i = 1<<i;
1431 GlobalValueMap.insert(std::make_pair(idName, i));
1432 idx++;
1435 fl.SkipSpaces();
1436 int ch = fl.Get();
1437 if (ch != EOF && ch != ';') fl.Unget(ch);
1438 //if (fl.ReadWord() != ";") ABORT("';' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1439 continue;
1441 if (word == "define") {
1442 fl.ReadWord(word);
1443 sLong v = fl.ReadNumber();
1444 GlobalValueMap.insert(std::make_pair(word, v));
1445 continue;
1447 ABORT("Illegal datafile define in file %s on line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1452 void game::InitGlobalValueMap () {
1453 inputfile SaveFile(GetGameDir()+"Script/define.dat", &GlobalValueMap);
1454 LoadGlobalValueMap(SaveFile);
1455 { /* additional files */
1456 for (int f = 0; f <= 99; f++) {
1457 char bnum[32];
1458 sprintf(bnum, "Script/define_%02d.dat", f);
1459 festring fn = game::GetGameDir();
1460 fn << bnum;
1461 if (inputfile::fileExists(fn)) return;
1462 inputfile ifl(fn, &game::GetGlobalValueMap(), false);
1463 if (ifl.IsOpen()) {
1464 LoadGlobalValueMap(ifl);
1465 ifl.Close();
1472 void game::TextScreen (cfestring &Text, v2 Displacement, col16 Color, truth GKey, truth Fade, bitmapeditor BitmapEditor) {
1473 globalwindowhandler::DisableControlLoops();
1474 iosystem::TextScreen(Text, Displacement, Color, GKey, Fade, BitmapEditor);
1475 globalwindowhandler::EnableControlLoops();
1479 /* ... all the keys that are acceptable
1480 DefaultAnswer = REQUIRES_ANSWER if this question requires an answer
1481 Not surprisingly KeyNumber is the number of keys at ...
1483 int game::KeyQuestion (cfestring &Message, int DefaultAnswer, int KeyNumber, ...) {
1484 int *Key = new int[KeyNumber];
1485 va_list Arguments;
1486 va_start(Arguments, KeyNumber);
1487 for (int c = 0; c < KeyNumber; ++c) Key[c] = va_arg(Arguments, int);
1488 va_end(Arguments);
1489 DrawEverythingNoBlit();
1490 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Message.CStr());
1491 graphics::BlitDBToScreen();
1492 int Return = 0;
1493 while (!Return) {
1494 int k = GET_KEY();
1495 for (int c = 0; c < KeyNumber; ++c) {
1496 if (Key[c] == k) {
1497 Return = k;
1498 break;
1501 if (!Return && DefaultAnswer != REQUIRES_ANSWER) Return = DefaultAnswer;
1503 delete [] Key;
1504 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1505 return Return;
1509 v2 game::LookKeyHandler (v2 CursorPos, int Key) {
1510 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1511 switch (Key) {
1512 case 'i':
1513 if (!IsInWilderness()) {
1514 if (Square->CanBeSeenByPlayer() || CursorPos == Player->GetPos() || GetSeeWholeMapCheatMode()) {
1515 lsquare *LSquare = GetCurrentLevel()->GetLSquare(CursorPos);
1516 stack *Stack = LSquare->GetStack();
1517 if (LSquare->IsTransparent() && Stack->GetVisibleItems(Player))
1518 Stack->DrawContents(Player, "Items here", NO_SELECT|(GetSeeWholeMapCheatMode() ? 0 : NO_SPECIAL_INFO));
1519 else
1520 ADD_MESSAGE("You see no items here.");
1521 } else ADD_MESSAGE("You should perhaps move a bit closer.");
1523 break;
1524 case 'c':
1525 if (Square->CanBeSeenByPlayer() || CursorPos == Player->GetPos() || GetSeeWholeMapCheatMode()) {
1526 character *Char = Square->GetCharacter();
1527 if (Char && (Char->CanBeSeenByPlayer() || Char->IsPlayer() || GetSeeWholeMapCheatMode()))
1528 Char->PrintInfo();
1529 else
1530 ADD_MESSAGE("You see no one here.");
1531 } else ADD_MESSAGE("You should perhaps move a bit closer.");
1532 break;
1534 return CursorPos;
1538 v2 game::NameKeyHandler (v2 CursorPos, int Key) {
1539 if (SelectPet(Key)) return LastPetUnderCursor->GetPos();
1540 if (Key == 'n' || Key == 'N') {
1541 character *Char = GetCurrentArea()->GetSquare(CursorPos)->GetCharacter();
1542 if (Char && Char->CanBeSeenByPlayer()) Char->TryToName();
1543 else ADD_MESSAGE("You don't see anyone here to name.");
1545 return CursorPos;
1549 void game::End (festring DeathMessage, truth Permanently, truth AndGoToMenu) {
1550 pool::AbortBe();
1551 globalwindowhandler::DeInstallControlLoop(AnimationController);
1552 SetIsRunning(false);
1553 if (Permanently || !WizardModeIsReallyActive()) RemoveSaves(Permanently);
1554 if (Permanently && !WizardModeIsReallyActive()) {
1555 highscore HScore;
1557 if (HScore.LastAddFailed()) {
1558 iosystem::TextScreen(CONST_S("You didn't manage to get onto the high score list.\n\n\n\n")+GetPlayerName()+", "+DeathMessage+"\nRIP");
1559 } else {
1560 HScore.Draw();
1563 if (AndGoToMenu) {
1564 /* This prevents monster movement etc. after death. */
1565 throw quitrequest();
1570 int game::CalculateRoughDirection (v2 Vector) {
1571 if (!Vector.X && !Vector.Y) return YOURSELF;
1572 double Angle = femath::CalculateAngle(Vector);
1573 if (Angle < FPI / 8) return 4;
1574 else if (Angle < 3*FPI/8) return 7;
1575 else if (Angle < 5*FPI/8) return 6;
1576 else if (Angle < 7*FPI/8) return 5;
1577 else if (Angle < 9*FPI/8) return 3;
1578 else if (Angle < 11*FPI/8) return 0;
1579 else if (Angle < 13*FPI/8) return 1;
1580 else if (Angle < 15*FPI/8) return 2;
1581 else return 4;
1585 int game::Menu (bitmap *BackGround, v2 Pos, cfestring &Topic, cfestring &sMS, col16 Color, cfestring &SmallText1, cfestring &SmallText2) {
1586 globalwindowhandler::DisableControlLoops();
1587 int Return = iosystem::Menu(BackGround, Pos, Topic, sMS, Color, SmallText1, SmallText2);
1588 globalwindowhandler::EnableControlLoops();
1589 return Return;
1593 void game::InitDangerMap () {
1594 truth First = true;
1595 pool::RegisterState(false);
1596 //fprintf(stderr, "game::InitDangerMap(): START\n");
1597 for (int c1 = 1; c1 < protocontainer<character>::GetSize(); ++c1) {
1598 BusyAnimation();
1599 const character::prototype *Proto = protocontainer<character>::GetProto(c1);
1600 const character::database *const *ConfigData = Proto->GetConfigData();
1601 int ConfigSize = Proto->GetConfigSize();
1602 for (int c2 = 0; c2 < ConfigSize; ++c2) {
1603 if (!ConfigData[c2]->IsAbstract) {
1604 int Config = ConfigData[c2]->Config;
1606 if (First) {
1607 NextDangerIDType = c1;
1608 NextDangerIDConfigIndex = c2;
1609 First = false;
1611 character *Char = Proto->Spawn(Config, NO_EQUIPMENT|NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1612 double NakedDanger = Char->GetRelativeDanger(Player, true);
1613 //fprintf(stderr, " game::InitDangerMap(): 00\n");
1614 delete Char;
1615 //Char->SendToHell(); // k8: equipment
1616 Char = Proto->Spawn(Config, NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1617 double EquippedDanger = Char->GetRelativeDanger(Player, true);
1618 //fprintf(stderr, " game::InitDangerMap(): 01\n");
1619 delete Char;
1620 //Char->SendToHell(); // k8: equipment
1621 DangerMap[configid(c1, Config)] = dangerid(NakedDanger, EquippedDanger);
1625 pool::RegisterState(true);
1626 //fprintf(stderr, "game::InitDangerMap(): DONE\n");
1630 void game::CalculateNextDanger () {
1631 if (IsInWilderness() || !*CurrentLevel->GetLevelScript()->GenerateMonsters()) return;
1632 const character::prototype *Proto = protocontainer<character>::GetProto(NextDangerIDType);
1633 const character::database *const *ConfigData = Proto->GetConfigData();
1634 const character::database *DataBase = ConfigData[NextDangerIDConfigIndex];
1635 dangermap::iterator DangerIterator = DangerMap.find(configid(NextDangerIDType, DataBase->Config));
1636 team *Team = GetTeam(PLAYER_TEAM);
1637 if (DataBase && DangerIterator != DangerMap.end()) {
1638 //fprintf(stderr, "game::CalculateNextDanger(): START\n");
1639 pool::RegisterState(false);
1640 character *Char = Proto->Spawn(DataBase->Config, NO_EQUIPMENT|NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1641 std::list<character*>::const_iterator i;
1642 double DangerSum = Player->GetRelativeDanger(Char, true);
1643 for (i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i) {
1644 if ((*i)->IsEnabled() && !(*i)->IsTemporary() && !RAND_N(10)) DangerSum += (*i)->GetRelativeDanger(Char, true)/4;
1646 double CurrentDanger = 1/DangerSum;
1647 double NakedDanger = DangerIterator->second.NakedDanger;
1648 //fprintf(stderr, " game::CalculateNextDanger(): 00\n");
1649 delete Char;
1650 //Char->SendToHell(); // k8: equipment
1651 if (NakedDanger > CurrentDanger) DangerIterator->second.NakedDanger = (NakedDanger*9+CurrentDanger)/10;
1652 Char = Proto->Spawn(DataBase->Config, NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1653 DangerSum = Player->GetRelativeDanger(Char, true);
1654 for (i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i) {
1655 if ((*i)->IsEnabled() && !(*i)->IsTemporary() && !RAND_N(10)) DangerSum += (*i)->GetRelativeDanger(Char, true) / 4;
1657 CurrentDanger = 1/DangerSum;
1658 double EquippedDanger = DangerIterator->second.EquippedDanger;
1659 //fprintf(stderr, " game::CalculateNextDanger(): 01\n");
1660 delete Char;
1661 //Char->SendToHell(); // k8: equipment
1662 pool::RegisterState(true);
1663 if (EquippedDanger > CurrentDanger) DangerIterator->second.EquippedDanger = (EquippedDanger*9+CurrentDanger)/10;
1664 if (++NextDangerIDConfigIndex < Proto->GetConfigSize()) {
1665 //fprintf(stderr, "game::CalculateNextDanger(): EXIT0\n");
1666 return;
1668 for (;;) {
1669 if (++NextDangerIDType >= protocontainer<character>::GetSize()) NextDangerIDType = 1;
1670 Proto = protocontainer<character>::GetProto(NextDangerIDType);
1671 ConfigData = Proto->GetConfigData();
1672 int ConfigSize = Proto->GetConfigSize();
1674 for (int c = 0; c < ConfigSize; ++c) {
1675 if (!ConfigData[c]->IsAbstract) {
1676 NextDangerIDConfigIndex = c;
1677 //fprintf(stderr, "game::CalculateNextDanger(): EXIT1\n");
1678 return;
1682 //fprintf(stderr, "game::CalculateNextDanger(): DONE\n");
1683 } else {
1684 ABORT("It is dangerous to go ice fishing in the summer.");
1689 truth game::TryTravel (int Dungeon, int Area, int EntryIndex, truth AllowHostiles, truth AlliesFollow) {
1690 charactervector Group;
1691 if (LeaveArea(Group, AllowHostiles, AlliesFollow)) {
1692 CurrentDungeonIndex = Dungeon;
1693 EnterArea(Group, Area, EntryIndex);
1694 return true;
1696 return false;
1700 truth game::LeaveArea (charactervector &Group, truth AllowHostiles, truth AlliesFollow) {
1701 if (!IsInWilderness()) {
1702 if (AlliesFollow && !GetCurrentLevel()->CollectCreatures(Group, Player, AllowHostiles)) return false;
1703 Player->Remove();
1704 GetCurrentDungeon()->SaveLevel(SaveName(), CurrentLevelIndex);
1705 } else {
1706 Player->Remove();
1707 GetWorldMap()->GetPlayerGroup().swap(Group);
1708 SaveWorldMap();
1710 return true;
1714 /* Used always when the player enters an area. */
1715 void game::EnterArea (charactervector &Group, int Area, int EntryIndex) {
1716 if (Area != WORLD_MAP) {
1717 Generating = true;
1718 SetIsInWilderness(false);
1719 CurrentLevelIndex = Area;
1720 truth New = !PrepareRandomBone(Area) && !GetCurrentDungeon()->PrepareLevel(Area);
1721 igraph::CreateBackGround(*CurrentLevel->GetLevelScript()->GetBackGroundType());
1722 GetCurrentArea()->SendNewDrawRequest();
1723 v2 Pos = GetCurrentLevel()->GetEntryPos(Player, EntryIndex);
1724 if (Player) {
1725 GetCurrentLevel()->GetLSquare(Pos)->KickAnyoneStandingHereAway();
1726 Player->PutToOrNear(Pos);
1727 } else SetPlayer(GetCurrentLevel()->GetLSquare(Pos)->GetCharacter());
1728 uInt c;
1729 for (c = 0; c < Group.size(); ++c) {
1730 v2 NPCPos = GetCurrentLevel()->GetNearestFreeSquare(Group[c], Pos);
1731 if (NPCPos == ERROR_V2) NPCPos = GetCurrentLevel()->GetRandomSquare(Group[c]);
1732 Group[c]->PutTo(NPCPos);
1734 GetCurrentLevel()->FiatLux();
1735 ctruth *AutoReveal = GetCurrentLevel()->GetLevelScript()->AutoReveal();
1736 if (New && AutoReveal && *AutoReveal) GetCurrentLevel()->Reveal();
1737 ShowLevelMessage();
1738 SendLOSUpdateRequest();
1739 UpdateCamera();
1741 /* Gum solution! */
1742 if (New && CurrentDungeonIndex == ATTNAM && Area == 0) {
1743 GlobalRainLiquid = powder::Spawn(SNOW);
1744 GlobalRainSpeed = v2(-64, 128);
1745 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
1748 if (New && CurrentDungeonIndex == NEW_ATTNAM && Area == 0) {
1749 GlobalRainLiquid = liquid::Spawn(WATER);
1750 GlobalRainSpeed = v2(256, 512);
1751 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
1754 if (New && CurrentDungeonIndex == ELPURI_CAVE && Area == OREE_LAIR) {
1755 GlobalRainLiquid = liquid::Spawn(BLOOD);
1756 GlobalRainSpeed = v2(256, 512);
1757 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
1758 GlobalRainLiquid->SetVolumeNoSignals(200);
1759 CurrentLevel->EnableGlobalRain();
1762 if (New && CurrentDungeonIndex == MUNTUO && Area == 0) {
1763 GlobalRainLiquid = liquid::Spawn(WATER);
1764 GlobalRainSpeed = v2(-64, 1024);
1765 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
1768 Generating = false;
1769 GetCurrentLevel()->UpdateLOS();
1770 Player->SignalStepFrom(0);
1772 for (c = 0; c < Group.size(); ++c) Group[c]->SignalStepFrom(0);
1774 if (ivanconfig::GetAutoSaveInterval()) Save(GetAutoSaveFileName().CStr());
1775 } else {
1776 igraph::CreateBackGround(GRAY_FRACTAL);
1777 SetIsInWilderness(true);
1778 LoadWorldMap();
1779 SetCurrentArea(WorldMap);
1780 CurrentWSquareMap = WorldMap->GetMap();
1781 GetWorldMap()->GetPlayerGroup().swap(Group);
1782 Player->PutTo(GetWorldMap()->GetEntryPos(Player, EntryIndex));
1783 SendLOSUpdateRequest();
1784 UpdateCamera();
1785 GetWorldMap()->UpdateLOS();
1786 if (ivanconfig::GetAutoSaveInterval()) Save(GetAutoSaveFileName().CStr());
1791 int game::CompareLightToInt (col24 L, col24 Int) {
1792 if ((L & 0xFF0000) > Int || (L & 0xFF00) > Int || (L & 0xFF) > Int) return 1;
1793 if ((L & 0xFF0000) == Int || (L & 0xFF00) == Int || (L & 0xFF) == Int) return 0;
1794 return -1;
1798 void game::SetStandardListAttributes (felist &List) {
1799 List.SetPos(v2(26, 42));
1800 List.SetWidth(652);
1801 List.SetFlags(DRAW_BACKGROUND_AFTERWARDS);
1802 List.SetUpKey(GetMoveCommandKey(KEY_UP_INDEX));
1803 List.SetDownKey(GetMoveCommandKey(KEY_DOWN_INDEX));
1807 void game::InitPlayerAttributeAverage () {
1808 AveragePlayerArmStrengthExperience
1809 = AveragePlayerLegStrengthExperience
1810 = AveragePlayerDexterityExperience
1811 = AveragePlayerAgilityExperience
1812 = 0;
1814 if (!Player->IsHumanoid()) return;
1816 humanoid *Player = static_cast<humanoid*>(GetPlayer());
1817 int Arms = 0;
1818 int Legs = 0;
1819 arm *RightArm = Player->GetRightArm();
1821 if (RightArm && !RightArm->UseMaterialAttributes()) {
1822 AveragePlayerArmStrengthExperience += RightArm->GetStrengthExperience();
1823 AveragePlayerDexterityExperience += RightArm->GetDexterityExperience();
1824 ++Arms;
1827 arm *LeftArm = Player->GetLeftArm();
1829 if (LeftArm && !LeftArm->UseMaterialAttributes()) {
1830 AveragePlayerArmStrengthExperience += LeftArm->GetStrengthExperience();
1831 AveragePlayerDexterityExperience += LeftArm->GetDexterityExperience();
1832 ++Arms;
1835 leg *RightLeg = Player->GetRightLeg();
1837 if (RightLeg && !RightLeg->UseMaterialAttributes()) {
1838 AveragePlayerLegStrengthExperience += RightLeg->GetStrengthExperience();
1839 AveragePlayerAgilityExperience += RightLeg->GetAgilityExperience();
1840 ++Legs;
1843 leg *LeftLeg = Player->GetLeftLeg();
1845 if (LeftLeg && !LeftLeg->UseMaterialAttributes()) {
1846 AveragePlayerLegStrengthExperience += LeftLeg->GetStrengthExperience();
1847 AveragePlayerAgilityExperience += LeftLeg->GetAgilityExperience();
1848 ++Legs;
1851 if (Arms) {
1852 AveragePlayerArmStrengthExperience /= Arms;
1853 AveragePlayerDexterityExperience /= Arms;
1856 if (Legs) {
1857 AveragePlayerLegStrengthExperience /= Legs;
1858 AveragePlayerAgilityExperience /= Legs;
1863 void game::UpdatePlayerAttributeAverage () {
1864 if (!Player->IsHumanoid()) return;
1866 humanoid *Player = static_cast<humanoid*>(GetPlayer());
1867 double PlayerArmStrengthExperience = 0;
1868 double PlayerLegStrengthExperience = 0;
1869 double PlayerDexterityExperience = 0;
1870 double PlayerAgilityExperience = 0;
1871 int Arms = 0;
1872 int Legs = 0;
1873 arm *RightArm = Player->GetRightArm();
1875 if (RightArm && !RightArm->UseMaterialAttributes()) {
1876 PlayerArmStrengthExperience += RightArm->GetStrengthExperience();
1877 PlayerDexterityExperience += RightArm->GetDexterityExperience();
1878 ++Arms;
1881 arm *LeftArm = Player->GetLeftArm();
1883 if (LeftArm && !LeftArm->UseMaterialAttributes()) {
1884 PlayerArmStrengthExperience += LeftArm->GetStrengthExperience();
1885 PlayerDexterityExperience += LeftArm->GetDexterityExperience();
1886 ++Arms;
1889 leg *RightLeg = Player->GetRightLeg();
1891 if (RightLeg && !RightLeg->UseMaterialAttributes()) {
1892 PlayerLegStrengthExperience += RightLeg->GetStrengthExperience();
1893 PlayerAgilityExperience += RightLeg->GetAgilityExperience();
1894 ++Legs;
1897 leg *LeftLeg = Player->GetLeftLeg();
1899 if (LeftLeg && !LeftLeg->UseMaterialAttributes()) {
1900 PlayerLegStrengthExperience += LeftLeg->GetStrengthExperience();
1901 PlayerAgilityExperience += LeftLeg->GetAgilityExperience();
1902 ++Legs;
1905 if (Arms) {
1906 AveragePlayerArmStrengthExperience = (49 * AveragePlayerArmStrengthExperience + PlayerArmStrengthExperience / Arms) / 50;
1907 AveragePlayerDexterityExperience = (49 * AveragePlayerDexterityExperience + PlayerDexterityExperience / Arms) / 50;
1910 if (Legs) {
1911 AveragePlayerLegStrengthExperience = (49 * AveragePlayerLegStrengthExperience + PlayerLegStrengthExperience / Legs) / 50;
1912 AveragePlayerAgilityExperience = (49 * AveragePlayerAgilityExperience + PlayerAgilityExperience / Legs) / 50;
1917 void game::CallForAttention (v2 Pos, int RangeSquare) {
1918 for (int c = 0; c < GetTeams(); ++c) {
1919 if (GetTeam(c)->HasEnemy())
1920 for (std::list<character*>::const_iterator i = GetTeam(c)->GetMember().begin(); i != GetTeam(c)->GetMember().end(); ++i)
1921 if ((*i)->IsEnabled()) {
1922 sLong ThisDistance = HypotSquare(sLong((*i)->GetPos().X) - Pos.X, sLong((*i)->GetPos().Y) - Pos.Y);
1923 if (ThisDistance <= RangeSquare && !(*i)->IsGoingSomeWhere()) (*i)->SetGoingTo(Pos);
1929 outputfile &operator << (outputfile &SaveFile, const homedata *HomeData) {
1930 if (HomeData) {
1931 SaveFile.Put(1);
1932 SaveFile << HomeData->Pos << HomeData->Dungeon << HomeData->Level << HomeData->Room;
1933 } else SaveFile.Put(0);
1934 return SaveFile;
1938 inputfile &operator >> (inputfile &SaveFile, homedata *&HomeData) {
1939 if (SaveFile.Get()) {
1940 HomeData = new homedata;
1941 SaveFile >> HomeData->Pos >> HomeData->Dungeon >> HomeData->Level >> HomeData->Room;
1943 return SaveFile;
1947 feuLong game::CreateNewCharacterID (character *NewChar) {
1948 feuLong ID = NextCharacterID++;
1949 /*k8:??? if(CharacterIDMap.find(ID) != CharacterIDMap.end())
1950 int esko = esko = 2;*/
1951 CharacterIDMap.insert(std::make_pair(ID, NewChar));
1952 return ID;
1956 feuLong game::CreateNewItemID (item *NewItem) {
1957 feuLong ID = NextItemID++;
1958 /*k8:??? if(ItemIDMap.find(ID) != ItemIDMap.end())
1959 int esko = esko = 2;*/
1960 if (NewItem) ItemIDMap.insert(std::make_pair(ID, NewItem));
1961 return ID;
1965 feuLong game::CreateNewTrapID (entity *NewTrap) {
1966 feuLong ID = NextTrapID++;
1967 /*k8:??? if(TrapIDMap.find(ID) != TrapIDMap.end())
1968 int esko = esko = 2;*/
1969 if (NewTrap) TrapIDMap.insert(std::make_pair(ID, NewTrap));
1970 return ID;
1974 character *game::SearchCharacter (feuLong ID) {
1975 characteridmap::iterator Iterator = CharacterIDMap.find(ID);
1976 return Iterator != CharacterIDMap.end() ? Iterator->second : 0;
1980 item *game::SearchItem (feuLong ID) {
1981 itemidmap::iterator Iterator = ItemIDMap.find(ID);
1982 return Iterator != ItemIDMap.end() ? Iterator->second : 0;
1986 entity *game::SearchTrap (feuLong ID) {
1987 trapidmap::iterator Iterator = TrapIDMap.find(ID);
1988 return Iterator != TrapIDMap.end() ? Iterator->second : 0;
1992 outputfile &operator << (outputfile &SaveFile, const configid &Value) {
1993 SaveFile.Write(reinterpret_cast<cchar*>(&Value), sizeof(Value));
1994 return SaveFile;
1998 inputfile &operator >> (inputfile &SaveFile, configid &Value) {
1999 SaveFile.Read(reinterpret_cast<char*>(&Value), sizeof(Value));
2000 return SaveFile;
2004 outputfile &operator << (outputfile &SaveFile, const dangerid &Value) {
2005 SaveFile << Value.NakedDanger << Value.EquippedDanger;
2006 return SaveFile;
2010 inputfile &operator >> (inputfile &SaveFile, dangerid &Value) {
2011 SaveFile >> Value.NakedDanger >> Value.EquippedDanger;
2012 return SaveFile;
2016 /* The program can only create directories to the deepness of one, no more... */
2017 festring game::GetHomeDir () {
2018 festring Dir;
2019 Dir << getenv("HOME") << '/';
2020 return Dir;
2024 festring game::GetSaveDir () {
2025 festring Dir;
2026 #ifdef LOCAL_SAVES
2027 Dir << ivanconfig::GetMyDir() << "/Save/";
2028 #else
2029 Dir << getenv("HOME") << "/IvanSave/";
2030 #endif
2031 return Dir;
2035 festring game::GetGameDir () {
2036 /*k8! return DATADIR "/ivan/"; */
2037 /*k8! return DATADIR "/"; */
2038 festring Dir;
2039 Dir << ivanconfig::GetMyDir() << "/";
2040 return Dir;
2044 festring game::GetBoneDir () {
2045 /*k8! return LOCAL_STATE_DIR "/Bones/";*/
2046 festring Dir;
2047 #ifdef LOCAL_SAVES
2048 Dir << ivanconfig::GetMyDir() << "/Save/Bones/";
2049 #else
2050 Dir << getenv("HOME") << "/IvanSave/Bones/";
2051 #endif
2052 return Dir;
2056 level *game::GetLevel (int I) {
2057 return GetCurrentDungeon()->GetLevel(I);
2061 int game::GetLevels () {
2062 return GetCurrentDungeon()->GetLevels();
2066 void game::SignalDeath (ccharacter *Ghost, ccharacter *Murderer, festring DeathMsg) {
2067 if (InWilderness) DeathMsg << " in the world map";
2068 else DeathMsg << " in " << GetCurrentDungeon()->GetLevelDescription(CurrentLevelIndex);
2069 massacremap *MassacreMap;
2070 if (!Murderer) {
2071 ++MiscMassacreAmount;
2072 MassacreMap = &MiscMassacreMap;
2073 } else if(Murderer->IsPlayer()) {
2074 ++PlayerMassacreAmount;
2075 MassacreMap = &PlayerMassacreMap;
2076 } else if(Murderer->IsPet()) {
2077 ++PetMassacreAmount;
2078 MassacreMap = &PetMassacreMap;
2079 } else {
2080 ++MiscMassacreAmount;
2081 MassacreMap = &MiscMassacreMap;
2084 massacreid MI(Ghost->GetType(), Ghost->GetConfig(), Ghost->GetAssignedName());
2085 massacremap::iterator i = MassacreMap->find(MI);
2087 if (i == MassacreMap->end()) {
2088 i = MassacreMap->insert(std::make_pair(MI, killdata(1, Ghost->GetGenerationDanger()))).first;
2089 i->second.Reason.push_back(killreason(DeathMsg, 1));
2090 } else {
2091 ++i->second.Amount;
2092 i->second.DangerSum += Ghost->GetGenerationDanger();
2093 std::vector<killreason>& Reason = i->second.Reason;
2094 uInt c;
2095 for (c = 0; c < Reason.size(); ++c) {
2096 if (Reason[c].String == DeathMsg) {
2097 ++Reason[c].Amount;
2098 break;
2101 if (c == Reason.size()) Reason.push_back(killreason(DeathMsg, 1));
2106 void game::DisplayMassacreLists () {
2107 DisplayMassacreList(PlayerMassacreMap, "directly by you.", PlayerMassacreAmount);
2108 DisplayMassacreList(PetMassacreMap, "by your allies.", PetMassacreAmount);
2109 DisplayMassacreList(MiscMassacreMap, "by some other reason.", MiscMassacreAmount);
2113 struct massacresetentry {
2114 bool operator < (const massacresetentry &MSE) const { return festring::IgnoreCaseCompare(Key, MSE.Key); }
2115 festring Key;
2116 festring String;
2117 std::vector<festring> Details;
2118 int ImageKey;
2122 void game::DisplayMassacreList (const massacremap &MassacreMap, cchar *Reason, sLong Amount) {
2123 std::set<massacresetentry> MassacreSet;
2124 festring FirstPronoun;
2125 truth First = true;
2126 charactervector GraveYard;
2128 for (massacremap::const_iterator i1 = MassacreMap.begin(); i1 != MassacreMap.end(); ++i1) {
2129 character *Victim = protocontainer<character>::GetProto(i1->first.Type)->Spawn(i1->first.Config);
2130 Victim->SetAssignedName(i1->first.Name);
2131 massacresetentry Entry;
2132 GraveYard.push_back(Victim);
2133 Entry.ImageKey = AddToCharacterDrawVector(Victim);
2134 if (i1->second.Amount == 1) {
2135 Victim->AddName(Entry.Key, UNARTICLED);
2136 Victim->AddName(Entry.String, INDEFINITE);
2137 } else {
2138 Victim->AddName(Entry.Key, PLURAL);
2139 Entry.String << i1->second.Amount << ' ' << Entry.Key;
2141 if (First) {
2142 FirstPronoun = Victim->GetSex() == UNDEFINED ? "it" : Victim->GetSex() == MALE ? "he" : "she";
2143 First = false;
2145 const std::vector<killreason>& Reason = i1->second.Reason;
2146 std::vector<festring>& Details = Entry.Details;
2147 if (Reason.size() == 1) {
2148 festring Begin;
2149 if (Reason[0].Amount == 1) Begin = "";
2150 else if(Reason[0].Amount == 2) Begin = "both ";
2151 else Begin = "all ";
2152 Details.push_back(Begin + Reason[0].String);
2153 } else {
2154 for (uInt c = 0; c < Reason.size(); ++c) Details.push_back(CONST_S("")+Reason[c].Amount+' '+Reason[c].String);
2155 std::sort(Details.begin(), Details.end(), ignorecaseorderer());
2157 MassacreSet.insert(Entry);
2159 sLong Total = PlayerMassacreAmount+PetMassacreAmount+MiscMassacreAmount;
2160 festring MainTopic;
2161 if (Total == 1) MainTopic << "One creature perished during your adventure.";
2162 else MainTopic << Total << " creatures perished during your adventure.";
2163 felist List(MainTopic);
2164 SetStandardListAttributes(List);
2165 List.SetPageLength(15);
2166 List.AddFlags(SELECTABLE);
2167 List.SetEntryDrawer(CharacterEntryDrawer);
2168 List.AddDescription(CONST_S(""));
2169 festring SideTopic;
2170 if (Amount != Total) {
2171 SideTopic = CONST_S("The following ");
2172 if (Amount == 1) SideTopic << "one was killed " << Reason;
2173 else SideTopic << Amount << " were killed " << Reason;
2174 } else {
2175 if (Amount == 1) {
2176 FirstPronoun.Capitalize();
2177 SideTopic << FirstPronoun << " was killed " << Reason;
2178 } else SideTopic << "They were all killed " << Reason;
2180 List.AddDescription(SideTopic);
2181 List.AddDescription(CONST_S(""));
2182 List.AddDescription("Choose a type of creatures to browse death details.");
2183 std::set<massacresetentry>::const_iterator i2;
2184 for (i2 = MassacreSet.begin(); i2 != MassacreSet.end(); ++i2) List.AddEntry(i2->String, LIGHT_GRAY, 0, i2->ImageKey);
2185 for (;;) {
2186 int Chosen = List.Draw();
2187 if (Chosen & FELIST_ERROR_BIT) break;
2188 felist SubList(CONST_S("Massacre details"));
2189 SetStandardListAttributes(SubList);
2190 SubList.SetPageLength(20);
2191 int Counter = 0;
2192 for (i2 = MassacreSet.begin(); i2 != MassacreSet.end(); ++i2, ++Counter) {
2193 if (Counter == Chosen) {
2194 for (uInt c = 0; c < i2->Details.size(); ++c) SubList.AddEntry(i2->Details[c], LIGHT_GRAY);
2195 break;
2198 SubList.Draw();
2200 ClearCharacterDrawVector();
2201 for (uInt c = 0; c < GraveYard.size(); ++c) delete GraveYard[c];
2205 truth game::MassacreListsEmpty () {
2206 return PlayerMassacreMap.empty() && PetMassacreMap.empty() && MiscMassacreMap.empty();
2210 #ifdef WIZARD
2211 void game::SeeWholeMap () {
2212 if (SeeWholeMapCheatMode < 2) ++SeeWholeMapCheatMode; else SeeWholeMapCheatMode = 0;
2213 GetCurrentArea()->SendNewDrawRequest();
2215 #endif
2218 void game::CreateBone () {
2219 if (!WizardModeIsActive() && !IsInWilderness() && RAND() & 3 && GetCurrentLevel()->PreProcessForBone()) {
2220 int BoneIndex;
2221 festring BoneName;
2222 for (BoneIndex = 0; BoneIndex < 1000; ++BoneIndex) {
2223 BoneName = GetBoneDir()+"bon"+CurrentDungeonIndex+CurrentLevelIndex+BoneIndex;
2224 if (!inputfile::fileExists(BoneName)) break;
2226 if (BoneIndex != 1000) {
2227 //festring BoneName = GetBoneDir()+"bon"+CurrentDungeonIndex+CurrentLevelIndex+BoneIndex;
2228 fprintf(stderr, "creating bone file: [%s]\n", BoneName.CStr());
2229 outputfile BoneFile(BoneName, true);
2230 BoneFile << int(BONE_FILE_VERSION) << PlayerName << CurrentLevel;
2236 truth game::PrepareRandomBone (int LevelIndex) {
2237 if (/*k8:WizardModeIsActive() ||*/ GetCurrentDungeon()->IsGenerated(LevelIndex) || !*GetCurrentDungeon()->GetLevelScript(LevelIndex)->CanGenerateBone()) return false;
2238 int BoneIndex;
2239 festring BoneName;
2240 for (BoneIndex = 0; BoneIndex < 1000; ++BoneIndex) {
2241 BoneName = GetBoneDir()+"bon"+CurrentDungeonIndex+LevelIndex+BoneIndex;
2242 inputfile BoneFile(BoneName, 0, false);
2243 if (BoneFile.IsOpen() && !(RAND() & 7)) {
2244 if (ReadType<int>(BoneFile) != BONE_FILE_VERSION) {
2245 BoneFile.Close();
2246 remove(BoneName.CStr());
2247 continue;
2249 festring Name;
2250 BoneFile >> Name;
2251 level *NewLevel = GetCurrentDungeon()->LoadLevel(BoneFile, LevelIndex);
2252 if (!NewLevel->PostProcessForBone()) {
2253 delete NewLevel;
2254 GetBoneItemIDMap().clear();
2255 GetBoneCharacterIDMap().clear();
2256 continue;
2258 NewLevel->FinalProcessForBone();
2259 GetBoneItemIDMap().clear();
2260 GetBoneCharacterIDMap().clear();
2261 SetCurrentArea(NewLevel);
2262 CurrentLevel = NewLevel;
2263 CurrentLSquareMap = NewLevel->GetMap();
2264 GetCurrentDungeon()->SetIsGenerated(LevelIndex, true);
2265 if (Name == PlayerName) ADD_MESSAGE("This place is oddly familiar. Like you had been here in one of your past lives.");
2266 else ADD_MESSAGE("You smell the stench of death.");
2267 break;
2270 Generating = true;
2271 if (BoneIndex != 1000) {
2272 remove(BoneName.CStr());
2273 return true;
2275 return false;
2279 double game::CalculateAverageDanger (const charactervector &EnemyVector, character *Char) {
2280 double DangerSum = 0;
2281 int Enemies = 0;
2282 for (uInt c = 0; c < EnemyVector.size(); ++c) {
2283 DangerSum += EnemyVector[c]->GetRelativeDanger(Char, true);
2284 ++Enemies;
2286 return DangerSum/Enemies;
2290 double game::CalculateAverageDangerOfAllNormalEnemies () {
2291 double DangerSum = 0;
2292 int Enemies = 0;
2293 for (int c1 = 1; c1 < protocontainer<character>::GetSize(); ++c1) {
2294 const character::prototype *Proto = protocontainer<character>::GetProto(c1);
2295 const character::database*const *ConfigData = Proto->GetConfigData();
2296 int ConfigSize = Proto->GetConfigSize();
2297 for (int c2 = 0; c2 < ConfigSize; ++c2) {
2298 if (!ConfigData[c2]->IsAbstract && !ConfigData[c2]->IsUnique && ConfigData[c2]->CanBeGenerated) {
2299 DangerSum += DangerMap.find(configid(c1, ConfigData[c2]->Config))->second.EquippedDanger;
2300 ++Enemies;
2304 return DangerSum/Enemies;
2308 character *game::CreateGhost () {
2309 double AverageDanger = CalculateAverageDangerOfAllNormalEnemies();
2310 charactervector EnemyVector;
2311 protosystem::CreateEveryNormalEnemy(EnemyVector);
2312 ghost *Ghost = ghost::Spawn();
2313 Ghost->SetTeam(GetTeam(MONSTER_TEAM));
2314 Ghost->SetGenerationDanger(CurrentLevel->GetDifficulty());
2315 Ghost->SetOwnerSoul(PlayerName);
2316 Ghost->SetIsActive(false);
2317 Ghost->EditAllAttributes(-4);
2318 Player->SetSoulID(Ghost->GetID());
2319 while (CalculateAverageDanger(EnemyVector, Ghost) > AverageDanger && Ghost->EditAllAttributes(1));
2320 for (uInt c = 0; c < EnemyVector.size(); ++c) delete EnemyVector[c];
2321 return Ghost;
2325 int game::GetMoveCommandKey (int I) {
2326 if (!ivanconfig::GetUseAlternativeKeys()) return MoveNormalCommandKey[I];
2327 return MoveAbnormalCommandKey[I];
2331 sLong game::GetScore () {
2332 double Counter = 0;
2333 massacremap::const_iterator i;
2334 massacremap SumMap = PlayerMassacreMap;
2335 for (i = PetMassacreMap.begin(); i != PetMassacreMap.end(); ++i) {
2336 killdata &KillData = SumMap[i->first];
2337 KillData.Amount += i->second.Amount;
2338 KillData.DangerSum += i->second.DangerSum;
2340 for (i = SumMap.begin(); i != SumMap.end(); ++i) {
2341 character *Char = protocontainer<character>::GetProto(i->first.Type)->Spawn(i->first.Config);
2342 int SumOfAttributes = Char->GetSumOfAttributes();
2343 Counter += sqrt(i->second.DangerSum / DEFAULT_GENERATION_DANGER) * SumOfAttributes * SumOfAttributes;
2344 delete Char;
2346 return sLong(0.01*Counter);
2350 /* Only works if New Attnam is loaded */
2351 truth game::TweraifIsFree () {
2352 for (std::list<character*>::const_iterator i = GetTeam(COLONIST_TEAM)->GetMember().begin(); i != GetTeam(COLONIST_TEAM)->GetMember().end(); ++i)
2353 if ((*i)->IsEnabled()) return false;
2354 return true;
2358 // returns true if date is christmaseve or day
2359 truth game::IsXMas () {
2360 time_t Time = time(0);
2361 struct tm *TM = localtime(&Time);
2362 return (TM->tm_mon == 11 && (TM->tm_mday == 24 || TM->tm_mday == 25));
2366 int game::AddToItemDrawVector (const itemvector &What) {
2367 ItemDrawVector.push_back(What);
2368 return ItemDrawVector.size()-1;
2372 v2 ItemDisplacement[3][3] = {
2373 { v2(0, 0), ERROR_V2, ERROR_V2 },
2374 { v2(-2, -2), v2(2, 2), ERROR_V2 },
2375 { v2(-4, -4), v2(0, 0), v2(4, 4) }
2379 void game::ItemEntryDrawer (bitmap *Bitmap, v2 Pos, uInt I) {
2380 blitdata B = {
2381 Bitmap,
2382 { 0, 0 },
2383 { 0, 0 },
2384 { TILE_SIZE, TILE_SIZE },
2385 { NORMAL_LUMINANCE },
2386 TRANSPARENT_COLOR,
2387 ALLOW_ANIMATE
2389 itemvector ItemVector = ItemDrawVector[I];
2390 int Amount = Min<int>(ItemVector.size(), 3);
2391 for (int c = 0; c < Amount; ++c) {
2392 v2 Displacement = ItemDisplacement[Amount-1][c];
2393 if (!ItemVector[0]->HasNormalPictureDirection()) Displacement.X = -Displacement.X;
2394 B.Dest = Pos+Displacement;
2395 if (ItemVector[c]->AllowAlphaEverywhere()) B.CustomData |= ALLOW_ALPHA;
2396 ItemVector[c]->Draw(B);
2397 B.CustomData &= ~ALLOW_ALPHA;
2399 if (ItemVector.size() > 3) {
2400 B.Src.X = 0;
2401 B.Src.Y = 16;
2402 B.Dest = ItemVector[0]->HasNormalPictureDirection() ? Pos+v2(11, -2) : Pos+v2(-2, -2);
2403 B.Flags = 0;
2404 igraph::GetSymbolGraphic()->NormalMaskedBlit(B);
2409 int game::AddToCharacterDrawVector (character *What) {
2410 CharacterDrawVector.push_back(What);
2411 return CharacterDrawVector.size()-1;
2415 void game::CharacterEntryDrawer (bitmap *Bitmap, v2 Pos, uInt I) {
2416 if (CharacterDrawVector[I]) {
2417 blitdata B = {
2418 Bitmap,
2419 { 0, 0 },
2420 { Pos.X, Pos.Y },
2421 { TILE_SIZE, TILE_SIZE },
2422 { NORMAL_LUMINANCE },
2423 TRANSPARENT_COLOR,
2424 ALLOW_ANIMATE|ALLOW_ALPHA
2426 CharacterDrawVector[I]->DrawBodyParts(B);
2431 void game::GodEntryDrawer (bitmap *Bitmap, v2 Pos, uInt I) {
2432 blitdata B = {
2433 Bitmap,
2434 { I << 4, 0 },
2435 { Pos.X, Pos.Y },
2436 { TILE_SIZE, TILE_SIZE },
2437 { 0 },
2438 TRANSPARENT_COLOR,
2441 igraph::GetSymbolGraphic()->NormalMaskedBlit(B);
2445 character *game::GetSumo () {
2446 return GetCurrentLevel()->GetLSquare(SUMO_ROOM_POS)->GetRoom()->GetMaster();
2450 truth game::TryToEnterSumoArena () {
2451 character *Sumo = GetSumo();
2452 if (!Sumo || !Sumo->IsEnabled() || Sumo->GetRelation(Player) == HOSTILE || !Player->CanBeSeenBy(Sumo)) return true;
2453 if (TweraifIsFree()) {
2454 ADD_MESSAGE("\"You started this stupid revolution, after which I've been constantly hungry. Get lost!\"");
2455 return false;
2457 if (PlayerIsSumoChampion()) {
2458 ADD_MESSAGE("\"I don't really enjoy losing, especially many times to the same guy. Go away.\"");
2459 return false;
2461 if (Player->IsPolymorphed()) {
2462 ADD_MESSAGE("\"Don't try to cheat. Come back when you're normal again.\"");
2463 return false;
2465 if (Player->GetHungerState() < SATIATED) {
2466 ADD_MESSAGE("\"Your figure is too slender for this sport. Eat a lot more and come back.\"");
2467 return false;
2469 if (Player->GetHungerState() < BLOATED) {
2470 ADD_MESSAGE("\"You're still somewhat too thin. Eat some more and we'll compete.\"");
2471 return false;
2473 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.\"");
2474 if (!TruthQuestion("Do you want to challenge him? [y/N]")) return false;
2475 pool::AbortBe();
2476 SumoWrestling = true;
2477 character *MirrorPlayer = Player->Duplicate(IGNORE_PROHIBITIONS);
2478 character *MirrorSumo = Sumo->Duplicate(IGNORE_PROHIBITIONS);
2479 SetPlayer(MirrorPlayer);
2480 charactervector Spectators;
2481 if (Player->GetTeam()->GetRelation(GetTeam(TOURIST_GUIDE_TEAM)) != HOSTILE &&
2482 Player->GetTeam()->GetRelation(GetTeam(TOURIST_TEAM)) != HOSTILE) {
2483 GetTeam(TOURIST_GUIDE_TEAM)->MoveMembersTo(Spectators);
2484 GetTeam(TOURIST_TEAM)->MoveMembersTo(Spectators);
2486 GetCurrentDungeon()->SaveLevel(SaveName(), 0);
2487 charactervector test;
2488 EnterArea(test, 1, STAIRS_UP);
2489 MirrorSumo->PutTo(SUMO_ARENA_POS+v2(6, 5));
2490 MirrorSumo->ChangeTeam(GetTeam(SUMO_TEAM));
2491 GetCurrentLevel()->GetLSquare(SUMO_ARENA_POS)->GetRoom()->SetMasterID(MirrorSumo->GetID());
2492 for (uInt c = 0; c < Spectators.size(); ++c) Spectators[c]->PutToOrNear(SUMO_ARENA_POS + v2(6, 10));
2493 throw areachangerequest();
2494 return true;
2498 truth game::TryToExitSumoArena () {
2499 if (GetTeam(PLAYER_TEAM)->GetRelation(GetTeam(NEW_ATTNAM_TEAM)) == HOSTILE) return true;
2500 itemvector IVector;
2501 charactervector CVector;
2502 if (IsSumoWrestling()) {
2503 if (TruthQuestion("Do you really wish to give up? [y/N]")) return EndSumoWrestling(LOST);
2504 return false;
2505 } else {
2506 pool::AbortBe();
2507 Player->Remove();
2508 GetCurrentLevel()->CollectEverything(IVector, CVector);
2509 GetCurrentDungeon()->SaveLevel(SaveName(), 1);
2510 std::vector<character*> test;
2511 EnterArea(test, 0, STAIRS_DOWN);
2512 Player->GetStackUnder()->AddItems(IVector);
2513 if (!IVector.empty()) {
2514 character *Sumo = GetSumo();
2515 if (Sumo && Sumo->GetRelation(Player) != HOSTILE && Player->CanBeSeenBy(Sumo)) ADD_MESSAGE("\"Don't leave anything there, please.\"");
2517 v2 PlayerPos = Player->GetPos();
2518 for (uInt c = 0; c < CVector.size(); ++c) CVector[c]->PutNear(PlayerPos);
2519 throw areachangerequest();
2520 return true;
2525 truth game::EndSumoWrestling (int Result) {
2526 pool::AbortBe();
2527 msgsystem::LeaveBigMessageMode();
2528 if (Result == LOST) AskForKeyPress("You lose. [press any key to continue]");
2529 else if (Result == WON) AskForKeyPress("You win! [press any key to continue]");
2530 else if (Result == DISQUALIFIED) AskForKeyPress("You are disqualified! [press any key to continue]");
2531 character *Sumo = GetCurrentLevel()->GetLSquare(SUMO_ARENA_POS)->GetRoom()->GetMaster();
2532 /* We'll make a throw soon so deletes are allowed */
2533 if (Sumo) {
2534 Sumo->Remove();
2535 delete Sumo;
2537 Player->Remove();
2538 delete Player;
2539 SetPlayer(0);
2540 itemvector IVector;
2541 charactervector CVector;
2542 GetCurrentLevel()->CollectEverything(IVector, CVector);
2543 GetCurrentDungeon()->SaveLevel(SaveName(), 1);
2544 charactervector test;
2545 EnterArea(test, 0, STAIRS_DOWN);
2546 SumoWrestling = false;
2547 Player->GetStackUnder()->AddItems(IVector);
2548 v2 PlayerPos = Player->GetPos();
2549 for (uInt c = 0; c < CVector.size(); ++c) CVector[c]->PutNear(PlayerPos);
2550 if (Result == LOST) ADD_MESSAGE("\"I hope you've learned your lesson now!\"");
2551 else if (Result == DISQUALIFIED) ADD_MESSAGE("\"Don't do that again or I'll be really angry!\"");
2552 else {
2553 PlayerSumoChampion = true;
2554 character *Sumo = GetSumo();
2555 festring Msg = Sumo->GetName(DEFINITE)+" seems humbler than before. \"Darn. You bested me.\n";
2556 Msg << "Here's a little something as a reward\", " << Sumo->GetPersonalPronoun() << " says and hands you a belt of levitation.\n\"";
2557 (belt::Spawn(BELT_OF_LEVITATION))->MoveTo(Player->GetStack());
2558 Msg << "Allow me to also teach you a few nasty martial art tricks the years have taught me.\"";
2559 Player->GetCWeaponSkill(UNARMED)->AddHit(100000);
2560 Player->GetCWeaponSkill(KICK)->AddHit(100000);
2561 character *Imperialist = GetCurrentLevel()->GetLSquare(5, 5)->GetRoom()->GetMaster();
2562 if (Imperialist && Imperialist->GetRelation(Player) != HOSTILE) {
2563 v2 Pos = Player->GetPos()+v2(0, 1);
2564 GetCurrentLevel()->GetLSquare(Pos)->KickAnyoneStandingHereAway();
2565 Imperialist->Remove();
2566 Imperialist->PutTo(Pos);
2567 Msg << "\n\nSuddenly you notice " << Imperialist->GetName(DEFINITE) << " has also entered.\n"
2568 "\"I see we have a promising fighter among us. I had already heard of your\n"
2569 "adventures outside the village, but hardly could I believe that one day you\n"
2570 "would defeat even the mighty Huang Ming Pong! A hero such as you is bound\n"
2571 "to become world famous, and can earn a fortune if wealthy sponsors are behind\n"
2572 "him. May I therefore propose a mutually profitable contract: I'll give you this\n"
2573 "nice shirt with my company's ad, and you'll wear it as you journey bravely to\n"
2574 "the unknown and fight epic battles against the limitless minions of evil. I'll\n"
2575 "reward you well when you return, depending on how much you have used it.\"";
2576 Player->GetStack()->AddItem(decosadshirt::Spawn());
2578 TextScreen(Msg);
2579 GetCurrentArea()->SendNewDrawRequest();
2580 DrawEverything();
2582 Player->EditNP(-25000);
2583 Player->CheckStarvationDeath(CONST_S("exhausted after controlling a mirror image for too long"));
2584 throw areachangerequest();
2585 return true;
2589 rain *game::ConstructGlobalRain () {
2590 return new rain(GlobalRainLiquid, static_cast<lsquare*>(GetSquareInLoad()), GlobalRainSpeed, MONSTER_TEAM, false);
2594 v2 game::GetSunLightDirectionVector () {
2595 int Index = Tick % 48000 / 1000;
2596 /* Should have the same sign as sin(PI * Index / 24) and XTable[Index] /
2597 YTable[Index] should equal roughly -tan(PI * Index / 24). Also, vector
2598 (XTable[Index], YTable[Index]) + P should not be a valid position of
2599 any possible level L for any P belonging to L. */
2600 static int XTable[48] = {
2601 0, 1000, 1000, 1000, 1000, 1000,
2602 1000, 1303, 1732, 2414, 3732, 7596,
2603 1000, 7596, 3732, 2414, 1732, 1303,
2604 1000, 1000, 1000, 1000, 1000, 1000,
2605 0, -1000, -1000, -1000, -1000, -1000,
2606 -1000, -1303, -1732, -2414, -3732, -7596,
2607 -1000, -7596, -3732, -2414, -1732, -1303,
2608 -1000, -1000, -1000, -1000, -1000, -1000 };
2609 /* Should have the same sign as -cos(PI * Index / 24) */
2610 static int YTable[48] = { -1000, -7596, -3732, -2414, -1732, -1303,
2611 -1000, -1000, -1000, -1000, -1000, -1000,
2612 0, 1000, 1000, 1000, 1000, 1000,
2613 1000, 1303, 1732, 2414, 3732, 7596,
2614 1000, 7596, 3732, 2414, 1732, 1303,
2615 1000, 1000, 1000, 1000, 1000, 1000,
2616 0, -1000, -1000, -1000, -1000, -1000,
2617 -1000, -1303, -1732, -2414, -3732, -7596 };
2618 return v2(XTable[Index], YTable[Index]);
2622 int game::CalculateMinimumEmitationRadius (col24 E) {
2623 int MaxElement = Max(GetRed24(E), GetGreen24(E), GetBlue24(E));
2624 return int(sqrt(double(MaxElement << 7) / LIGHT_BORDER - 120.));
2628 feuLong game::IncreaseSquarePartEmitationTicks () {
2629 if ((SquarePartEmitationTick += 2) == 0x100) {
2630 CurrentLevel->InitSquarePartEmitationTicks();
2631 SquarePartEmitationTick = 2;
2633 return SquarePartEmitationTick;
2637 bool game::Wish (character *Wisher, cchar *MsgSingle, cchar *MsgPair, bool canAbort) {
2638 for (;;) {
2639 festring oldDef = DefaultWish;
2640 festring Temp = DefaultQuestion(CONST_S("What do you want to wish for?"), DefaultWish);
2641 if (DefaultWish == "nothing" && canAbort) {
2642 DefaultWish = oldDef;
2643 return false;
2645 item *TempItem = protosystem::CreateItem(Temp, Wisher->IsPlayer());
2646 if (TempItem) {
2647 Wisher->GetStack()->AddItem(TempItem);
2648 TempItem->SpecialGenerationHandler();
2649 if (TempItem->HandleInPairs()) ADD_MESSAGE(MsgPair, TempItem->CHAR_NAME(PLURAL));
2650 else ADD_MESSAGE(MsgSingle, TempItem->CHAR_NAME(INDEFINITE));
2651 return true;
2657 festring game::DefaultQuestion (festring Topic, festring &Default, stringkeyhandler KeyHandler) {
2658 festring ShortDefault = Default;
2659 if (Default.GetSize() > 29) {
2660 ShortDefault.Resize(27);
2661 ShortDefault = ShortDefault << CONST_S("...");
2663 if (!Default.IsEmpty()) Topic << " [" << ShortDefault << ']';
2664 festring Answer = StringQuestion(Topic, WHITE, 0, 80, false, KeyHandler);
2665 if (Answer.IsEmpty()) Answer = Default;
2666 return Default = Answer;
2670 void game::GetTime (ivantime &Time) {
2671 Time.Hour = 12 + Tick / 2000;
2672 Time.Day = Time.Hour / 24 + 1;
2673 Time.Hour %= 24;
2674 Time.Min = Tick % 2000 * 60 / 2000;
2678 truth NameOrderer (character *C1, character *C2) {
2679 return festring::IgnoreCaseCompare(C1->GetName(UNARTICLED), C2->GetName(UNARTICLED));
2683 truth game::PolymorphControlKeyHandler (int Key, festring &String) {
2684 if (Key == '?') {
2685 felist List(CONST_S("List of known creatures and their intelligence requirements"));
2686 SetStandardListAttributes(List);
2687 List.SetPageLength(15);
2688 List.AddFlags(SELECTABLE);
2689 protosystem::CreateEverySeenCharacter(CharacterDrawVector);
2690 std::sort(CharacterDrawVector.begin(), CharacterDrawVector.end(), NameOrderer);
2691 List.SetEntryDrawer(CharacterEntryDrawer);
2692 std::vector<festring> StringVector;
2693 uInt c;
2694 for (c = 0; c < CharacterDrawVector.size(); ++c) {
2695 character *Char = CharacterDrawVector[c];
2696 if (Char->CanBeWished()) {
2697 festring Entry;
2698 Char->AddName(Entry, UNARTICLED);
2699 StringVector.push_back(Entry);
2700 int Req = Char->GetPolymorphIntelligenceRequirement();
2701 if (Char->IsSameAs(Player) || (Player->GetPolymorphBackup() && Player->GetPolymorphBackup()->IsSameAs(Char))) Req = 0;
2702 Entry << " (" << Req << ')';
2703 int Int = Player->GetAttribute(INTELLIGENCE);
2704 List.AddEntry(Entry, Req > Int ? RED : LIGHT_GRAY, 0, c);
2707 int Chosen = List.Draw();
2708 for (c = 0; c < CharacterDrawVector.size(); ++c) delete CharacterDrawVector[c];
2709 if (!(Chosen & FELIST_ERROR_BIT)) String = StringVector[Chosen];
2710 CharacterDrawVector.clear();
2711 return true;
2713 return false;
2717 outputfile &operator << (outputfile &SaveFile, const killdata &Value) {
2718 SaveFile << Value.Amount << Value.DangerSum << Value.Reason;
2719 return SaveFile;
2723 inputfile &operator >> (inputfile &SaveFile, killdata &Value) {
2724 SaveFile >> Value.Amount >> Value.DangerSum >> Value.Reason;
2725 return SaveFile;
2729 outputfile &operator << (outputfile &SaveFile, const killreason &Value) {
2730 SaveFile << Value.Amount << Value.String;
2731 return SaveFile;
2735 inputfile &operator >> (inputfile &SaveFile, killreason &Value) {
2736 SaveFile >> Value.Amount >> Value.String;
2737 return SaveFile;
2741 truth DistanceOrderer (character *C1, character *C2) {
2742 v2 PlayerPos = PLAYER->GetPos();
2743 v2 Pos1 = C1->GetPos();
2744 v2 Pos2 = C2->GetPos();
2745 int D1 = Max(abs(Pos1.X - PlayerPos.X), abs(Pos1.Y - PlayerPos.Y));
2746 int D2 = Max(abs(Pos2.X - PlayerPos.X), abs(Pos2.Y - PlayerPos.Y));
2747 if (D1 != D2) return D1 < D2;
2748 if (Pos1.Y != Pos2.Y) return Pos1.Y < Pos2.Y;
2749 return Pos1.X < Pos2.X;
2753 truth game::FillPetVector (cchar *Verb) {
2754 PetVector.clear();
2755 team *Team = GetTeam(PLAYER_TEAM);
2756 for (std::list<character*>::const_iterator i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i)
2757 if ((*i)->IsEnabled() && !(*i)->IsPlayer() && (*i)->CanBeSeenByPlayer()) PetVector.push_back(*i);
2758 if (PetVector.empty()) {
2759 ADD_MESSAGE("You don't detect any friends to %s.", Verb);
2760 return false;
2762 std::sort(PetVector.begin(), PetVector.end(), DistanceOrderer);
2763 LastPetUnderCursor = PetVector[0];
2764 return true;
2768 truth game::CommandQuestion () {
2769 if (!FillPetVector("command")) return false;
2770 character *Char;
2771 if (PetVector.size() == 1) Char = PetVector[0];
2772 else {
2773 v2 Pos = PetVector[0]->GetPos();
2774 Pos = PositionQuestion(CONST_S("Whom do you wish to command? [direction keys/'+'/'-'/'a'll/space/esc]"), Pos, &PetHandler, &CommandKeyHandler);
2775 if (Pos == ERROR_V2) return false;
2776 if (Pos == ABORT_V2) return true;
2777 Char = CurrentArea->GetSquare(Pos)->GetCharacter();
2778 if (!Char || !Char->CanBeSeenByPlayer()) {
2779 ADD_MESSAGE("You don't see anyone here to command.");
2780 return false;
2782 if (Char->IsPlayer()) {
2783 ADD_MESSAGE("You do that all the time.");
2784 return false;
2786 if (!Char->IsPet()) {
2787 ADD_MESSAGE("%s refuses to be commanded by you.", Char->CHAR_NAME(DEFINITE));
2788 return false;
2791 return Char->IssuePetCommands();
2795 void game::NameQuestion () {
2796 if (!FillPetVector("name")) return;
2797 if (PetVector.size() == 1) PetVector[0]->TryToName();
2798 else PositionQuestion(CONST_S("Who do you want to name? [direction keys/'+'/'-'/'n'ame/esc]"), PetVector[0]->GetPos(), &PetHandler, &NameKeyHandler);
2802 void game::PetHandler (v2 CursorPos) {
2803 character *Char = CurrentArea->GetSquare(CursorPos)->GetCharacter();
2804 if (Char && Char->CanBeSeenByPlayer() && Char->IsPet() && !Char->IsPlayer()) CursorData = RED_CURSOR|CURSOR_TARGET;
2805 else CursorData = RED_CURSOR;
2806 if (Char && !Char->IsPlayer() && Char->IsPet()) LastPetUnderCursor = Char;
2810 v2 game::CommandKeyHandler (v2 CursorPos, int Key) {
2811 if (SelectPet(Key)) return LastPetUnderCursor->GetPos();
2812 if (Key == 'a' || Key == 'A') return CommandAll() ? ABORT_V2 : ERROR_V2;
2813 return CursorPos;
2817 truth game::SelectPet (int Key) {
2818 if (Key == '+') {
2819 for (uInt c = 0; c < PetVector.size(); ++c) {
2820 if (PetVector[c] == LastPetUnderCursor) {
2821 if (++c == PetVector.size()) c = 0;
2822 LastPetUnderCursor = PetVector[c];
2823 return true;
2826 } else if (Key == '-') {
2827 for (uInt c = 0; c < PetVector.size(); ++c) {
2828 if (PetVector[c] == LastPetUnderCursor) {
2829 if (!c) c = PetVector.size();
2830 LastPetUnderCursor = PetVector[--c];
2831 return true;
2835 return false;
2839 void game::CommandScreen (cfestring &Topic, feuLong PossibleFlags, feuLong ConstantFlags, feuLong &VaryFlags, feuLong &Flags) {
2840 static cchar *CommandDescription[COMMAND_FLAGS] = {
2841 "Follow me",
2842 "Flee from enemies",
2843 "Don't change your equipment",
2844 "Don't consume anything valuable"
2846 felist List(Topic);
2847 SetStandardListAttributes(List);
2848 List.AddFlags(SELECTABLE);
2849 List.AddDescription(CONST_S(""));
2850 List.AddDescription(CONST_S("Command Active?"));
2851 for (;;) {
2852 int c, i;
2853 for (c = 0; c < COMMAND_FLAGS; ++c) {
2854 if (1 << c & PossibleFlags) {
2855 truth Changeable = !(1 << c & ConstantFlags);
2856 festring Entry;
2857 if (Changeable) {
2858 Entry = CommandDescription[c];
2859 Entry.Resize(60);
2860 } else {
2861 Entry << " " << CommandDescription[c];
2862 Entry.Resize(63);
2864 if (1 << c & VaryFlags) Entry << "varies"; else Entry << (1 << c & Flags ? "yes" : "no");
2865 List.AddEntry(Entry, Changeable ? LIGHT_GRAY : DARK_GRAY, 0, NO_IMAGE, Changeable);
2868 int Chosen = List.Draw();
2869 if (Chosen & FELIST_ERROR_BIT) return;
2870 for (c = 0, i = 0; c < COMMAND_FLAGS; ++c) {
2871 if (1 << c & PossibleFlags && !(1 << c & ConstantFlags) && i++ == Chosen) {
2872 if (1 << c & VaryFlags) {
2873 VaryFlags &= ~(1 << c);
2874 Flags |= 1 << c;
2875 } else Flags ^= 1 << c;
2876 break;
2879 List.Empty();
2880 DrawEverythingNoBlit();
2885 truth game::CommandAll () {
2886 feuLong PossibleFlags = 0, ConstantFlags = ALL_COMMAND_FLAGS, VaryFlags = 0, OldFlags = 0;
2887 uInt c1, c2;
2888 for (c1 = 0; c1 < PetVector.size(); ++c1) {
2889 ConstantFlags &= PetVector[c1]->GetConstantCommandFlags();
2890 feuLong C = PetVector[c1]->GetCommandFlags();
2891 feuLong ThisPossible = PetVector[c1]->GetPossibleCommandFlags();
2892 for (c2 = 0; c2 < COMMAND_FLAGS; ++c2)
2893 if (1 << c2 & PossibleFlags & ThisPossible && (1 << c2 & C) != (1 << c2 & OldFlags)) VaryFlags |= 1 << c2;
2894 PossibleFlags |= ThisPossible;
2895 OldFlags |= C & ThisPossible;
2897 if (!PossibleFlags) {
2898 ADD_MESSAGE("Not a single creature in your visible team can be commanded.");
2899 return false;
2901 feuLong NewFlags = OldFlags;
2902 CommandScreen(CONST_S("Issue commands to whole visible team"), PossibleFlags, ConstantFlags, VaryFlags, NewFlags);
2903 truth Change = false;
2904 for (c1 = 0; c1 < PetVector.size(); ++c1) {
2905 character *Char = PetVector[c1];
2906 if (!Char->IsConscious()) continue;
2907 feuLong OldC = Char->GetCommandFlags();
2908 feuLong ConstC = Char->GetConstantCommandFlags();
2909 feuLong ThisC = (NewFlags & Char->GetPossibleCommandFlags() & ~(ConstC|VaryFlags)) | (OldC & (ConstC|VaryFlags));
2910 if (ThisC != OldC) Change = true;
2911 Char->SetCommandFlags(ThisC);
2913 if (!Change) return false;
2914 Player->EditAP(-500);
2915 Player->EditExperience(CHARISMA, 50, 1 << 7);
2916 return true;
2920 col16 game::GetAttributeColor (int I) {
2921 int Delta = GetTick()-LastAttributeChangeTick[I];
2922 if (OldAttribute[I] == NewAttribute[I] || Delta >= 510) return WHITE;
2923 if (OldAttribute[I] < NewAttribute[I]) return MakeRGB16(255, 255, Delta >> 1);
2924 return MakeRGB16(255, Delta >> 1, Delta >> 1);
2928 void game::UpdateAttributeMemory () {
2929 for (int c = 0; c < ATTRIBUTES; ++c) {
2930 int A = Player->GetAttribute(c);
2931 if (A != NewAttribute[c]) {
2932 OldAttribute[c] = NewAttribute[c];
2933 NewAttribute[c] = A;
2934 LastAttributeChangeTick[c] = GetTick();
2940 void game::InitAttributeMemory () {
2941 for (int c = 0; c < ATTRIBUTES; ++c) OldAttribute[c] = NewAttribute[c] = Player->GetAttribute(c);
2945 void game::TeleportHandler (v2 CursorPos) {
2946 if ((CursorPos-Player->GetPos()).GetLengthSquare() > Player->GetTeleportRangeSquare())
2947 CursorData = BLUE_CURSOR|CURSOR_TARGET;
2948 else
2949 CursorData = RED_CURSOR|CURSOR_TARGET;
2953 double game::GetGameSituationDanger () {
2954 double SituationDanger = 0;
2955 character *Player = GetPlayer();
2956 truth PlayerStuck = Player->IsStuck();
2957 v2 PlayerPos = Player->GetPos();
2958 character *TruePlayer = Player;
2959 if (PlayerStuck) (Player = Player->Duplicate(IGNORE_PROHIBITIONS))->ChangeTeam(0);
2960 for (int c1 = 0; c1 < GetTeams(); ++c1)
2961 if (GetTeam(c1)->GetRelation(GetTeam(PLAYER_TEAM)) == HOSTILE)
2962 for (std::list<character*>::const_iterator i1 = GetTeam(c1)->GetMember().begin(); i1 != GetTeam(c1)->GetMember().end(); ++i1) {
2963 character *Enemy = *i1;
2964 if (Enemy->IsEnabled() && Enemy->CanAttack() && (Enemy->CanMove() || Enemy->GetPos().IsAdjacent(PlayerPos))) {
2965 truth EnemyStuck = Enemy->IsStuck();
2966 v2 EnemyPos = Enemy->GetPos();
2967 truth Sees = TruePlayer->CanBeSeenBy(Enemy);
2968 character *TrueEnemy = Enemy;
2969 if (EnemyStuck) Enemy = Enemy->Duplicate(IGNORE_PROHIBITIONS);
2970 double PlayerTeamDanger = 1/Enemy->GetSituationDanger(Player, EnemyPos, PlayerPos, Sees);
2971 for (int c2 = 0; c2 < GetTeams(); ++c2)
2972 if (GetTeam(c2)->GetRelation(GetTeam(c1)) == HOSTILE)
2973 for (std::list<character*>::const_iterator i2 = GetTeam(c2)->GetMember().begin(); i2 != GetTeam(c2)->GetMember().end(); ++i2) {
2974 character *Friend = *i2;
2975 if (Friend->IsEnabled() && !Friend->IsPlayer() && Friend->CanAttack() && (Friend->CanMove() || Friend->GetPos().IsAdjacent(EnemyPos))) {
2976 v2 FriendPos = Friend->GetPos();
2977 truth Sees = TrueEnemy->CanBeSeenBy(Friend);
2978 if (Friend->IsStuck()) {
2979 Friend = Friend->Duplicate(IGNORE_PROHIBITIONS);
2980 PlayerTeamDanger += Friend->GetSituationDanger(Enemy, FriendPos, EnemyPos, Sees) * .2;
2981 delete Friend;
2982 } else PlayerTeamDanger += Friend->GetSituationDanger(Enemy, FriendPos, EnemyPos, Sees);
2985 if (EnemyStuck) {
2986 PlayerTeamDanger *= 5;
2987 delete Enemy;
2989 SituationDanger += 1 / PlayerTeamDanger;
2992 Player->ModifySituationDanger(SituationDanger);
2993 if (PlayerStuck) {
2994 SituationDanger *= 2;
2995 delete Player;
2997 return SituationDanger;
3001 sLong game::GetTimeSpent () {
3002 return time::TimeAdd(time::TimeDifference(time(0),LastLoad), TimePlayedBeforeLastLoad);
3006 outputfile &operator << (outputfile &SaveFile, const massacreid &MI) {
3007 SaveFile << MI.Type << MI.Config << MI.Name;
3008 return SaveFile;
3012 inputfile &operator >> (inputfile &SaveFile, massacreid &MI) {
3013 SaveFile >> MI.Type >> MI.Config >> MI.Name;
3014 return SaveFile;
3018 truth game::PlayerIsRunning () {
3019 return PlayerRunning && Player->CanMove();
3023 void game::AddSpecialCursor (v2 Pos, int Data) {
3024 SpecialCursorPos.push_back(Pos);
3025 SpecialCursorData.push_back(Data);
3029 void game::RemoveSpecialCursors () {
3030 SpecialCursorPos.clear();
3031 SpecialCursorData.clear();
3035 void game::LearnAbout (god *Who) {
3036 Who->SetIsKnown(true);
3037 /* slightly slow, but doesn't matter since this is run so rarely */
3038 if (PlayerKnowsAllGods() && !game::PlayerHasReceivedAllGodsKnownBonus) {
3039 GetPlayer()->ApplyAllGodsKnownBonus();
3040 game::PlayerHasReceivedAllGodsKnownBonus = true;
3045 truth game::PlayerKnowsAllGods () {
3046 for (int c = 1; c <= GODS; ++c) if (!GetGod(c)->IsKnown()) return false;
3047 return true;
3051 void game::AdjustRelationsToAllGods (int Amount) {
3052 for (int c = 1; c <= GODS; ++c) GetGod(c)->AdjustRelation(Amount);
3056 void game::SetRelationsToAllGods (int Amount) {
3057 for (int c = 1; c <= GODS; ++c) GetGod(c)->SetRelation(Amount);
3061 void game::ShowDeathSmiley (bitmap *Buffer, truth) {
3062 static blitdata B = {
3064 { 0, 0 },
3065 { (RES.X >> 1) - 24, RES.Y * 4 / 7 - 24 },
3066 { 48, 48 },
3067 { 0 },
3068 TRANSPARENT_COLOR,
3071 int Tick = globalwindowhandler::UpdateTick();
3072 if (((Tick >> 1) & 31) == 1) B.Src.X = 48;
3073 else if (((Tick >> 1) & 31) == 2) B.Src.X = 96;
3074 else B.Src.X = 0;
3075 B.Bitmap = Buffer;
3076 igraph::GetSmileyGraphic()->NormalBlit(B);
3077 if (Buffer == DOUBLE_BUFFER) graphics::BlitDBToScreen();
3081 static int doListSelector (felist &list, int defsel, int cnt) {
3082 game::SetStandardListAttributes(list);
3083 list.AddFlags(SELECTABLE | FELIST_NO_BADKEY_EXIT);
3084 if (defsel > 0) list.SetSelected(defsel);
3085 uInt sel = list.Draw();
3086 list.Empty();
3087 list.RemoveFlags(SELECTABLE | FELIST_NO_BADKEY_EXIT);
3088 if (sel & FELIST_ERROR_BIT) return -1;
3089 if (sel >= (uInt)cnt) return -1;
3090 return (int)sel;
3094 int game::ListSelector (int defsel, cfestring &title, ...) {
3095 int cnt = 0;
3096 va_list items;
3097 va_start(items, title);
3099 felist list(title);
3100 for (;;) {
3101 const char *s = va_arg(items, const char *);
3102 if (!s) break;
3103 list.AddEntry(s, LIGHT_GRAY);
3104 cnt++;
3106 va_end(items);
3107 return doListSelector(list, defsel, cnt);
3111 int game::ListSelectorArray (int defsel, cfestring &title, const char *items[]) {
3112 int cnt = 0;
3113 felist list(title);
3114 for (;;) {
3115 if (!items[cnt]) break;
3116 list.AddEntry(items[cnt], LIGHT_GRAY);
3117 cnt++;
3119 return doListSelector(list, defsel, cnt);
3123 void game::ClearEventData () {
3124 mChar = 0;
3125 mActor = 0;
3126 mSecondActor = 0;
3127 mItem = 0;
3131 // '.': string or number
3132 // 'n': number
3133 // 's': string
3134 // '*': collect all args
3135 int game::ParseFuncArgs (cfestring &types, std::vector<FuncArg> &args, inputfile *fl, truth noterm) {
3136 festring s;
3137 sLong n;
3138 truth isStr;
3139 if (!fl) fl = mFEStack.top();
3140 args.clear();
3141 for (unsigned int f = 0; f < types.GetSize(); f++) {
3142 switch (types[f]) {
3143 case '.':
3144 s = fl->ReadStringOrNumber(&n, &isStr, true);
3145 if (isStr) args.push_back(FuncArg(s)); else args.push_back(FuncArg(n));
3146 break;
3147 case 'n':
3148 n = fl->ReadNumber(0xFF, true);
3149 args.push_back(FuncArg(n));
3150 break;
3151 case '*':
3152 for (;;) {
3153 s = fl->ReadStringOrNumber(&n, &isStr, true);
3154 if (isStr) args.push_back(FuncArg(s)); else args.push_back(FuncArg(n));
3155 fl->ReadWord(s, true);
3156 if (s == ";") return args.size();
3157 if (s != ",") ABORT("',' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3159 // never reached
3160 case 's':
3161 default:
3162 s = fl->ReadWord(true);
3163 args.push_back(FuncArg(s));
3164 break;
3166 if (f == types.GetSize()-1) {
3167 if (noterm) break;
3168 fl->ReadWord(s, true);
3169 if (s != ";") ABORT("';' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3170 break;
3171 } else {
3172 fl->ReadWord(s, true);
3173 if (s != ",") ABORT("',' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3176 return args.size();
3180 truth game::GetWord (festring &w) {
3181 for (;;) {
3182 inputfile *fl = mFEStack.top();
3183 fl->ReadWord(w, false);
3184 if (w == "" && fl->Eof()) {
3185 delete fl;
3186 mFEStack.pop();
3187 if (mFEStack.empty()) return false;
3188 continue;
3190 if (w == "Include") {
3191 fl->ReadWord(w, true);
3192 if (fl->ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3193 w = game::GetGameDir()+"Script/"+w;
3194 inputfile *fl = new inputfile(w, &game::GetGlobalValueMap(), true);
3195 fl->setGetVarCB(game::ldrGetVar);
3196 mFEStack.push(fl);
3197 continue;
3199 if (w == "Message") {
3200 fl->ReadWord(w, true);
3201 if (fl->ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3202 fprintf(stderr, "MESSAGE: %s\n", w.CStr());
3203 continue;
3205 return true;
3210 void game::SkipBlock (truth brcEaten) {
3211 festring w;
3212 if (!brcEaten) {
3213 mFEStack.top()->ReadWord(w, true);
3214 if (w != "{") ABORT("'{' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3216 int cnt = 1;
3217 for (;;) {
3218 mFEStack.top()->ReadWord(w, true);
3219 if (w == "{") cnt++;
3220 else if (w == "}") {
3221 if (--cnt < 1) break;
3227 truth game::DoOnEvent (truth brcEaten, truth AllowScript) {
3228 // do; only funcalls for now
3229 truth eaten = AllowScript ? true : false;
3230 festring w;
3231 if (!brcEaten) {
3232 mFEStack.top()->ReadWord(w, true);
3233 if (w != "{") ABORT("'{' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3235 for (;;) {
3236 if (!GetWord(w)) {
3237 if (AllowScript) break;
3238 ABORT("Unexpected end of file %s!", mFEStack.top()->GetFileName().CStr());
3240 //fprintf(stderr, " :[%s]\n", w.CStr());
3241 if (w == "}") {
3242 if (AllowScript) ABORT("Unexpected '}' in AllowScript file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3243 break;
3245 if (w == ";") continue;
3246 if (w == "@") {
3247 mFEStack.top()->ReadWord(w, true);
3248 if (mFEStack.top()->ReadWord(true) != "=") ABORT("'=' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3249 //fprintf(stderr, "setvar: %s\n", w.CStr());
3250 if (w == "money") {
3251 sLong n = mFEStack.top()->ReadNumber(true);
3252 if (n < 0) n = 0;
3253 if (mChar) mChar->SetMoney(n);
3254 continue;
3256 if (w == "result") {
3257 mResult = mFEStack.top()->ReadNumber(true);
3258 continue;
3260 ABORT("Unknown var [%s] in file %s at line %d!", w.CStr(), mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3261 } else {
3262 //mFEStack.top()->ReadWord(w, true);
3263 std::vector<FuncArg> args;
3264 //fprintf(stderr, "funcall: %s\n", w.CStr());
3265 if (w == "SetMoney") {
3266 ParseFuncArgs("n", args);
3267 sLong n = args[0].ival;
3268 if (n < 0) n = 0;
3269 if (mChar) mChar->SetMoney(n);
3270 continue;
3272 if (w == "EditMoney") {
3273 ParseFuncArgs("n", args);
3274 sLong n = args[0].ival;
3275 if (mChar) mChar->EditMoney(n);
3276 continue;
3278 if (w == "AddMessage") {
3279 ParseFuncArgs("*", args);
3280 festring s;
3281 for (uInt f = 0; f < args.size(); f++) {
3282 const FuncArg &a = args[f];
3283 if (a.type == FARG_STRING) s << a.sval; else s << a.ival;
3285 ADD_MESSAGE("%s", s.CStr());
3286 continue;
3288 if (w == "EatThisEvent") {
3289 if (AllowScript) ABORT("'EatThisEvent' forbidden in AllowScripts in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3290 eaten = true;
3291 continue;
3293 if (w == "Disallow") {
3294 if (!AllowScript) ABORT("'Disallow' forbidden in not-AllowScripts in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3295 eaten = false;
3296 continue;
3298 ABORT("Unknown function [%s] in file %s at line %d!", w.CStr(), mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3299 //if (mFEStack.top()->ReadWord() != ";") ABORT("';' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3301 //ABORT("Invalid term in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3303 //fprintf(stderr, "------------\n");
3304 return eaten;
3308 //TODO: cache event scripts
3309 truth game::RunOnEvent (cfestring &ename) {
3310 static std::vector<festring> scriptFiles;
3311 static truth cached = false;
3312 truth res = false;
3314 character *old = mChar;
3315 mChar = PLAYER;
3316 if (!cached) {
3317 cached = true;
3318 for (int fno = 99; fno >= -1; fno--) {
3319 festring cfname;
3320 cfname << game::GetGameDir() << "Script/onevent";
3321 if (fno >= 0) {
3322 char bnum[8];
3323 sprintf(bnum, "_%02d", fno);
3324 cfname << bnum;
3326 cfname << ".dat";
3327 if (!inputfile::fileExists(cfname)) continue;
3328 inputfile *ifl = new inputfile(cfname, &game::GetGlobalValueMap(), false);
3329 if (!ifl->IsOpen()) {
3330 delete ifl;
3331 continue;
3333 scriptFiles.push_back(cfname);
3334 ifl->setGetVarCB(game::ldrGetVar);
3335 mFEStack.push(ifl);
3337 } else {
3338 for (unsigned int f = 0; f < scriptFiles.size(); ++f) {
3339 festring cfname = scriptFiles[f];
3340 inputfile *ifl = new inputfile(cfname, &game::GetGlobalValueMap(), false);
3341 if (!ifl->IsOpen()) {
3342 delete ifl;
3343 continue;
3345 ifl->setGetVarCB(game::ldrGetVar);
3346 mFEStack.push(ifl);
3350 festring w;
3351 while (GetWord(w)) {
3352 if (w != "on") ABORT("'on' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3353 mFEStack.top()->ReadWord(w, true);
3354 truth doIt = (w==ename);
3355 if (doIt && !res) {
3356 res = DoOnEvent(false);
3357 } else {
3358 // skip
3359 SkipBlock(false);
3362 mChar = old;
3363 return res;
3367 truth game::RunOnEventStr (cfestring &ename, cfestring &str) {
3368 truth res = false;
3369 if (str.GetSize() < 1) return false;
3370 //fprintf(stderr, "=============\n%s=============\n", str.CStr());
3371 inputfile *ifl = new meminputfile(str, &game::GetGlobalValueMap());
3372 ifl->setGetVarCB(game::ldrGetVar);
3373 mFEStack.push(ifl);
3374 festring w;
3375 //fprintf(stderr, "=============\n", str.CStr());
3376 //fprintf(stderr, "event: [%s]\n", ename.CStr());
3377 //fprintf(stderr, "---\n%s---\n", str.CStr());
3378 while (GetWord(w)) {
3379 if (w != "on") ABORT("'on' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3380 mFEStack.top()->ReadWord(w, true);
3381 //fprintf(stderr, "on: [%s]\n", w.CStr());
3382 truth doIt = (w==ename);
3383 if (doIt && !res) {
3384 //fprintf(stderr, " do it\n");
3385 res = DoOnEvent(false);
3386 } else {
3387 // skip
3388 //fprintf(stderr, " skip it\n");
3389 SkipBlock(false);
3392 return res;
3396 truth game::RunOnCharEvent (character *who, cfestring &ename) {
3397 truth res = false;
3398 if (!who) return false;
3399 character *old = mChar;
3400 mChar = who;
3401 res = RunOnEventStr(ename, who->mOnEvents);
3402 if (!res) res = RunOnEventStr(ename, who->GetProtoType()->mOnEvents);
3403 mChar = old;
3404 return res;
3408 truth game::RunOnItemEvent (item *what, cfestring &ename) {
3409 truth res = false;
3410 if (!what) return false;
3411 item *old = mItem;
3412 mItem = what;
3413 res = RunOnEventStr(ename, what->mOnEvents);
3414 if (!res) res = RunOnEventStr(ename, what->GetProtoType()->mOnEvents);
3415 mItem = old;
3416 return res;
3420 festring game::ldrGetVar (inputfile *fl, cfestring &name) {
3421 //fprintf(stderr, "GETVAR: [%s]\n", name.CStr());
3422 if (name == "player_name") {
3423 return game::GetPlayerName();
3425 if (name == "money") {
3426 festring res;
3427 if (!mChar) return "0";
3428 res << mChar->GetMoney();
3429 return res;
3431 if (name == "name") {
3432 if (!mChar) return "";
3433 return mChar->GetAssignedName();
3435 if (name == "team") {
3436 festring res;
3437 if (!mChar) return "";
3438 res << mChar->GetTeam()->GetID();
3439 return res;
3441 if (name == "friendly") {
3442 festring res;
3443 if (!mChar || !PLAYER || mChar->GetRelation(PLAYER) != HOSTILE) return "tan";
3444 return "";
3446 if (name == "hostile") {
3447 festring res;
3448 if (!mChar || !PLAYER) return "";
3449 if (mChar->GetRelation(PLAYER) == HOSTILE) return "tan";
3450 return "";
3452 if (name == "has_item") {
3453 std::vector<FuncArg> args;
3454 ParseFuncArgs("s", args, fl, true);
3456 if (PLAYER) {
3457 itemvector items;
3458 festring s = args[0].sval;
3460 //fprintf(stderr, "looking for [%s]\n", s.CStr());
3461 PLAYER->GetStack()->FillItemVector(items);
3462 for (unsigned int f = 0; f < items.size(); ++f) {
3463 for (uInt c = 0; c < items[f]->GetDataBase()->Alias.Size; ++c) {
3464 //fprintf(stderr, "%u:%u: [%s]\n", f, c, items[f]->GetDataBase()->Alias[c].CStr());
3465 if (s.CompareIgnoreCase(items[f]->GetDataBase()->Alias[c]) == 0) {
3466 //fprintf(stderr, " FOUND!\n");
3467 return "tan";
3471 //fprintf(stderr, "checking equipment...\n");
3472 for (int f = 0; f < PLAYER->GetEquipments(); ++f) {
3473 item *it = PLAYER->GetEquipment(f);
3475 if (it) {
3476 for (uInt c = 0; c < it->GetDataBase()->Alias.Size; ++c) {
3477 //fprintf(stderr, "%u:%u: [%s]\n", f, c, it->GetDataBase()->Alias[c].CStr());
3478 if (s.CompareIgnoreCase(it->GetDataBase()->Alias[c]) == 0) {
3479 //fprintf(stderr, " FOUND!\n");
3480 return "tan";
3486 return "";
3488 //if (name == "type") return mVarType;
3489 ABORT("unknown variable: %s", name.CStr());
3490 return "";
3494 truth game::CheckDropLeftover (item *i) {
3495 if (i->IsBottle() && !ivanconfig::GetAutoDropBottles()) return false;
3496 if (i->IsCan() && !ivanconfig::GetAutoDropCans()) return false;
3497 if (!ivanconfig::GetAutoDropLeftOvers()) return false;
3498 return true;
3502 truth game::RunAllowScriptStr (cfestring &str) {
3503 truth res = true;
3504 if (str.GetSize() < 1) return true;
3505 //fprintf(stderr, "====\n%s\n====\n", str.CStr());
3506 inputfile *ifl = new meminputfile(str, &game::GetGlobalValueMap());
3507 ifl->setGetVarCB(game::ldrGetVar);
3508 mFEStack.push(ifl);
3509 res = DoOnEvent(true, true);
3510 //fprintf(stderr, "mFEStack: %u\n", mFEStack.size());
3511 return res;