you don't want to know
[k8-i-v-a-n.git] / src / game / game.cpp
blob21a9419376c443baa6d41dbe304c75dc47156146
1 /*
3 * Iter Vehemens ad Necem (IVAN)
4 * Copyright (C) Timo Kiviluoto
5 * Released under the GNU General
6 * Public License
8 * See LICENSING which should be included
9 * along with this file for more details
13 #include <algorithm>
14 #include <cstdarg>
16 #include <sys/stat.h>
17 #include <sys/types.h>
18 #include "whandler.h"
19 #include "hscore.h"
20 #include "rawbit.h"
21 #include "message.h"
22 #include "feio.h"
23 #include "team.h"
24 #include "iconf.h"
25 #include "allocate.h"
26 #include "pool.h"
27 #include "god.h"
28 #include "proto.h"
29 #include "stack.h"
30 #include "felist.h"
31 #include "human.h"
32 #include "nonhuman.h"
33 #include "wsquare.h"
34 #include "game.h"
35 #include "graphics.h"
36 #include "bitmap.h"
37 #include "fesave.h"
38 #include "miscitem.h"
39 #include "room.h"
40 #include "materias.h"
41 #include "rain.h"
42 #include "gear.h"
43 #include "fetime.h"
44 #include "balance.h"
45 #include "confdef.h"
46 #include "wmapset.h"
48 #define SAVE_FILE_VERSION 130 // Increment this if changes make savefiles incompatible
49 #define BONE_FILE_VERSION 115 // Increment this if changes make bonefiles incompatible
51 #define LOADED 0
52 #define NEW_GAME 1
53 #define BACK 2
56 std::stack<inputfile *> game::mFEStack;
57 character *game::mChar = 0;
58 ccharacter *game::mActor = 0;
59 ccharacter *game::mSecondActor = 0;
60 item *game::mItem = 0;
61 int game::mResult = 0;
64 int game::CurrentLevelIndex;
65 truth game::InWilderness = false;
66 worldmap* game::WorldMap;
67 area* game::AreaInLoad;
68 square* game::SquareInLoad;
69 dungeon** game::Dungeon;
70 int game::CurrentDungeonIndex;
71 feuLong game::NextCharacterID = 1;
72 feuLong game::NextItemID = 1;
73 feuLong game::NextTrapID = 1;
74 team** game::Team;
75 feuLong game::LOSTick;
76 v2 game::CursorPos(-1, -1);
77 truth game::Zoom;
78 truth game::Generating = false;
79 double game::AveragePlayerArmStrengthExperience;
80 double game::AveragePlayerLegStrengthExperience;
81 double game::AveragePlayerDexterityExperience;
82 double game::AveragePlayerAgilityExperience;
83 int game::Teams;
84 int game::Dungeons;
85 int game::StoryState;
86 /* */
87 int game::MondedrPass;
88 int game::RingOfThieves;
89 int game::Masamune;
90 int game::Muramasa;
91 int game::LoricatusHammer;
92 int game::Liberator;
93 int game::OmmelBloodMission;
94 int game::RegiiTalkState;
95 /* */
96 massacremap game::PlayerMassacreMap;
97 massacremap game::PetMassacreMap;
98 massacremap game::MiscMassacreMap;
99 sLong game::PlayerMassacreAmount = 0;
100 sLong game::PetMassacreAmount = 0;
101 sLong game::MiscMassacreAmount = 0;
102 boneidmap game::BoneItemIDMap;
103 boneidmap game::BoneCharacterIDMap;
104 truth game::TooGreatDangerFoundTruth;
105 itemvectorvector game::ItemDrawVector;
106 charactervector game::CharacterDrawVector;
107 truth game::SumoWrestling;
108 liquid* game::GlobalRainLiquid;
109 v2 game::GlobalRainSpeed;
110 sLong game::GlobalRainTimeModifier;
111 truth game::PlayerSumoChampion;
112 truth game::PlayerSolicitusChampion;
113 feuLong game::SquarePartEmitationTick = 0;
114 sLong game::Turn;
115 truth game::PlayerRunning;
116 character* game::LastPetUnderCursor;
117 charactervector game::PetVector;
118 double game::DangerFound;
119 int game::OldAttribute[ATTRIBUTES];
120 int game::NewAttribute[ATTRIBUTES];
121 int game::LastAttributeChangeTick[ATTRIBUTES];
122 int game::NecroCounter;
123 int game::CursorData;
124 truth game::CausePanicFlag;
126 truth game::Loading = false;
127 truth game::JumpToPlayerBe = false;
128 truth game::InGetCommand = false;
129 character *game::Petrus = 0;
130 time_t game::TimePlayedBeforeLastLoad;
131 time_t game::LastLoad;
132 time_t game::GameBegan;
133 truth game::PlayerHasReceivedAllGodsKnownBonus;
135 festring game::AutoSaveFileName = game::GetSavePath()+"AutoSave";
136 cchar *const game::Alignment[] = { "L++", "L+", "L", "L-", "N+", "N=", "N-", "C+", "C", "C-", "C--" };
137 god **game::God;
139 cint game::MoveNormalCommandKey[] = { KEY_HOME, KEY_UP, KEY_PAGE_UP, KEY_LEFT, KEY_RIGHT, KEY_END, KEY_DOWN, KEY_PAGE_DOWN, '.' };
140 int game::MoveAbnormalCommandKey[] = { '7','8','9','u','o','j','k','l','.' };
142 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) };
143 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) };
144 cv2 game::BasicMoveVector[] = { v2(-1, 0), v2(1, 0), v2(0, -1), v2(0, 1) };
145 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) };
146 cint game::LargeMoveDirection[] = { 0, 1, 1, 2, 3, 4, 3, 4, 5, 6, 6, 7, 8, 8, 8, 8 };
148 truth game::LOSUpdateRequested = false;
149 uChar ***game::LuxTable = 0;
150 truth game::Running;
151 character *game::Player;
152 v2 game::Camera(0, 0);
153 feuLong game::Tick;
154 gamescript *game::GameScript = 0;
155 valuemap game::GlobalValueMap;
156 dangermap game::DangerMap;
157 int game::NextDangerIDType;
158 int game::NextDangerIDConfigIndex;
159 characteridmap game::CharacterIDMap;
160 itemidmap game::ItemIDMap;
161 trapidmap game::TrapIDMap;
162 truth game::PlayerHurtByExplosion;
163 area *game::CurrentArea;
164 level *game::CurrentLevel;
165 wsquare ***game::CurrentWSquareMap;
166 lsquare ***game::CurrentLSquareMap;
167 festring game::DefaultPolymorphTo;
168 festring game::DefaultSummonMonster;
169 festring game::DefaultWish;
170 festring game::DefaultChangeMaterial;
171 festring game::DefaultDetectMaterial;
172 festring game::DefaultTeam;
173 truth game::WizardMode;
174 int game::SeeWholeMapCheatMode;
175 truth game::GoThroughWallsCheat;
176 int game::QuestMonstersFound;
177 bitmap *game::BusyAnimationCache[32];
178 festring game::PlayerName;
179 feuLong game::EquipmentMemory[MAX_EQUIPMENT_SLOTS];
180 olterrain *game::MonsterPortal;
181 std::vector<v2> game::SpecialCursorPos;
182 std::vector<int> game::SpecialCursorData;
183 cbitmap *game::EnterImage;
184 v2 game::EnterTextDisplacement;
187 // -1: none
188 int game::MoveVectorToDirection (cv2 &mv) {
189 for (int c = 0; c < 9; ++c) if (MoveVector[c] == mv) return c;
190 return -1;
194 char game::GetAbnormalMoveKey (int idx) {
195 if (idx < 0 || idx > 8) return 0;
196 return MoveAbnormalCommandKey[idx];
200 void game::SetAbnormalMoveKey (int idx, char ch) {
201 if (idx >= 0 && idx <= 8) MoveAbnormalCommandKey[idx] = ch;
205 void game::AddCharacterID (character *Char, feuLong ID) {
206 /*k8:??? if (CharacterIDMap.find(ID) != CharacterIDMap.end())
207 int esko = esko = 2;*/
208 CharacterIDMap.insert(std::make_pair(ID, Char));
212 void game::RemoveCharacterID (feuLong ID) {
213 /*k8:??? if (CharacterIDMap.find(ID) == CharacterIDMap.end())
214 int esko = esko = 2;*/
215 CharacterIDMap.erase(CharacterIDMap.find(ID));
219 void game::AddItemID (item *Item, feuLong ID) {
220 /*k8:??? if (ItemIDMap.find(ID) != ItemIDMap.end())
221 int esko = esko = 2;*/
222 ItemIDMap.insert(std::make_pair(ID, Item));
226 void game::RemoveItemID (feuLong ID) {
227 /*k8:??? if(ID && ItemIDMap.find(ID) == ItemIDMap.end())
228 int esko = esko = 2;*/
229 if (ID) ItemIDMap.erase(ItemIDMap.find(ID));
233 void game::UpdateItemID (item *Item, feuLong ID) {
234 /*k8:??? if(ItemIDMap.find(ID) == ItemIDMap.end())
235 int esko = esko = 2;*/
236 ItemIDMap.find(ID)->second = Item;
240 void game::AddTrapID (entity *Trap, feuLong ID) {
241 /*k8:??? if(TrapIDMap.find(ID) != TrapIDMap.end())
242 int esko = esko = 2;*/
243 if (ID) TrapIDMap.insert(std::make_pair(ID, Trap));
247 void game::RemoveTrapID (feuLong ID) {
248 /*k8:??? if(ID && TrapIDMap.find(ID) == TrapIDMap.end())
249 int esko = esko = 2;*/
250 if (ID) TrapIDMap.erase(TrapIDMap.find(ID));
254 void game::UpdateTrapID (entity *Trap, feuLong ID) {
255 /*k8:??? if(TrapIDMap.find(ID) == TrapIDMap.end())
256 int esko = esko = 2;*/
257 TrapIDMap.find(ID)->second = Trap;
261 const dangermap &game::GetDangerMap () { return DangerMap; }
262 void game::ClearItemDrawVector () { ItemDrawVector.clear(); }
263 void game::ClearCharacterDrawVector () { CharacterDrawVector.clear(); }
266 void game::InitScript () {
267 inputfile ScriptFile(GetGameDir()+"script/dungeon.dat", &GlobalValueMap);
268 GameScript = new gamescript;
269 GameScript->ReadFrom(ScriptFile);
270 { /* additional dungeon files */
271 for (int f = 0; f <= 99; f++) {
272 char bnum[32];
273 sprintf(bnum, "script/dungeon_%02d.dat", f);
274 inputfile ifl(game::GetGameDir()+bnum, &game::GetGlobalValueMap(), false);
275 if (ifl.IsOpen()) {
276 //fprintf(stderr, "loading: %s\n", bnum+7);
277 GameScript->ReadFrom(ifl);
278 ifl.Close();
282 GameScript->RandomizeLevels();
286 truth game::Init (cfestring &Name) {
287 if (Name.IsEmpty()) {
288 if (ivanconfig::GetDefaultName().IsEmpty()) {
289 PlayerName.Empty();
290 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;
291 } else {
292 PlayerName = ivanconfig::GetDefaultName();
294 } else {
295 PlayerName = Name;
298 mkdir(GetSavePath().CStr(), S_IRWXU|S_IRWXG);
299 mkdir(GetBonePath().CStr(), S_IRWXU|S_IRWXG);
301 ::InitPlaces();
302 LOSTick = 2;
303 DangerFound = 0;
304 CausePanicFlag = false;
305 pool::KillBees();
306 //???
307 switch (Load(SaveName(PlayerName))) {
308 case LOADED: {
309 globalwindowhandler::InstallControlLoop(AnimationController);
310 SetIsRunning(true);
311 SetForceJumpToPlayerBe(true);
312 GetCurrentArea()->SendNewDrawRequest();
313 SendLOSUpdateRequest();
314 ADD_MESSAGE("Game loaded successfully.");
315 } return true;
316 case NEW_GAME: {
317 iosystem::TextScreen(CONST_S(
318 "You couldn't possibly have guessed this day would differ from any other.\n"
319 "It began just as always. You woke up at dawn and drove off the giant spider\n"
320 "resting on your face. On your way to work you had serious trouble avoiding\n"
321 "the lions and pythons roaming wild around the village. After getting kicked\n"
322 "by colony masters for being late you performed your twelve-hour routine of\n"
323 "climbing trees, gathering bananas, climbing trees, gathering bananas, chasing\n"
324 "monkeys that stole the first gathered bananas, carrying bananas to the village\n"
325 "and trying to look happy when real food was distributed.\n\n"
326 "Finally you were about to enjoy your free time by taking a quick dip in the\n"
327 "nearby crocodile bay. However, at this point something unusual happened.\n"
328 "You were summoned to the mansion of Richel Decos, the viceroy of the\n"
329 "colony, and were led directly to him."));
330 iosystem::TextScreen(CONST_S(
331 "\"I have a task for you, citizen\", said the viceroy picking his golden\n"
332 "teeth, \"The market price of bananas has taken a deep dive and yet the\n"
333 "central government is about to raise taxes. I have sent appeals to high\n"
334 "priest Petrus but received no response. I fear my enemies in Attnam are\n"
335 "plotting against me and intercepting my messages before they reach him!\"\n\n"
336 "\"That is why you must travel to Attnam with a letter I'll give you and\n"
337 "deliver it to Petrus directly. Alas, you somehow have to cross the sea\n"
338 "between. Because it's winter, all Attnamese ships are trapped by ice and\n"
339 "I have none. Therefore you must venture through the small underwater tunnel\n"
340 "connecting our islands. It is infested with monsters, but since you have\n"
341 "stayed alive here so long, the trip will surely cause you no trouble.\"\n\n"
342 "You have never been so happy! According to the mansion's traveling\n"
343 "brochures, Attnam is a peaceful but bustling world city on a beautiful\n"
344 "snowy fell surrounded by frozen lakes glittering in the arctic sun just\n"
345 "like the diamonds of the imperial treasury. Not that you would believe a\n"
346 "word. The point is that tomorrow you can finally forget your home and\n"
347 "face the untold adventures ahead."));
348 pool::RemoveEverything(); // memory leak!
349 CurrentLevel = 0;
350 globalwindowhandler::InstallControlLoop(AnimationController);
351 LOSTick = 2;
352 DangerFound = 0;
353 CausePanicFlag = false;
354 SetIsRunning(true);
355 InWilderness = true;
356 iosystem::TextScreen(CONST_S("Generating game...\n\nThis may take some time, please wait."), ZERO_V2, WHITE, false, true, &BusyAnimation);
357 igraph::CreateBackGround(GRAY_FRACTAL);
358 NextCharacterID = 1;
359 NextItemID = 1;
360 NextTrapID = 1;
361 InitScript();
362 CreateTeams();
363 CreateGods();
364 SetPlayer(playerkind::Spawn());
365 Player->SetAssignedName(PlayerName);
366 Player->SetTeam(GetTeam(PLAYER_TEAM));
367 Player->SetNP(SATIATED_LEVEL);
368 for (int c = 0; c < ATTRIBUTES; ++c) {
369 if (c != ENDURANCE) Player->EditAttribute(c, (RAND()&1)-(RAND()&1));
370 Player->EditExperience(c, 500, 1<<11);
372 Player->SetMoney(Player->GetMoney()+RAND()%11);
373 GetTeam(0)->SetLeader(Player);
374 InitDangerMap();
376 pool::KillBees();
377 if (Player->IsEnabled()) { Player->Disable(); Player->Enable(); }
379 Petrus = 0;
380 InitDungeons();
381 SetCurrentArea(WorldMap = new worldmap(128, 128));
382 CurrentWSquareMap = WorldMap->GetMap();
383 WorldMap->Generate();
384 UpdateCamera();
385 SendLOSUpdateRequest();
386 Tick = 0;
387 Turn = 0;
388 InitPlayerAttributeAverage();
389 StoryState = 0;
390 /* */
391 MondedrPass = 0;
392 RingOfThieves = 0;
393 Masamune = 0;
394 Muramasa = 0;
395 LoricatusHammer = 0;
396 Liberator = 0;
397 OmmelBloodMission = 0;
398 RegiiTalkState = 0;
399 /* */
400 PlayerMassacreMap.clear();
401 PetMassacreMap.clear();
402 MiscMassacreMap.clear();
403 PlayerMassacreAmount = PetMassacreAmount = MiscMassacreAmount = 0;
404 DefaultPolymorphTo.Empty();
405 DefaultSummonMonster.Empty();
406 DefaultWish.Empty();
407 DefaultChangeMaterial.Empty();
408 DefaultDetectMaterial.Empty();
409 DefaultTeam.Empty();
410 Player->GetStack()->AddItem(encryptedscroll::Spawn());
411 if (ivanconfig::GetDefaultPetName() != "_none_") {
412 character *Doggie = dog::Spawn();
413 Doggie->SetTeam(GetTeam(PLAYER_TEAM));
414 GetWorldMap()->GetPlayerGroup().push_back(Doggie);
415 Doggie->SetAssignedName(ivanconfig::GetDefaultPetName());
417 WizardMode = false;
418 SeeWholeMapCheatMode = MAP_HIDDEN;
419 GoThroughWallsCheat = false;
420 SumoWrestling = false;
421 GlobalRainTimeModifier = 2048-(RAND()&4095);
422 PlayerSumoChampion = false;
423 PlayerSolicitusChampion = false;
424 protosystem::InitCharacterDataBaseFlags();
425 memset(EquipmentMemory, 0, sizeof(EquipmentMemory));
426 PlayerRunning = false;
427 InitAttributeMemory();
428 NecroCounter = 0;
429 GameBegan = time(0);
430 LastLoad = time(0);
431 TimePlayedBeforeLastLoad = time::GetZeroTime();
432 /*k8: damn! seems that this is field, not local! bool PlayerHasReceivedAllGodsKnownBonus = false; */
433 PlayerHasReceivedAllGodsKnownBonus = false;
434 ADD_MESSAGE("You commence your journey to Attnam. Use direction keys to move, '>' to enter an area and '?' to view other commands.");
435 game::ClearEventData();
436 RunOnEvent("game_start");
437 if (IsXMas()) {
438 item *Present = banana::Spawn();
439 Player->GetStack()->AddItem(Present);
440 ADD_MESSAGE("Atavus is happy today! He gives you %s.", Present->CHAR_NAME(INDEFINITE));
442 } return true;
443 default: return false;
448 void game::DeInit () {
449 pool::BurnHell();
450 delete WorldMap;
451 WorldMap = 0;
452 if (Dungeon) {
453 for (int c = 1; c < Dungeons; ++c) delete Dungeon[c];
454 delete [] Dungeon;
455 Dungeon = 0;
457 if (God) {
458 for (int c = 1; c <= GODS; ++c) delete God[c]; // sorry, Valpuri!
459 delete [] God;
460 God = 0;
462 if (Team) {
463 for (int c = 0; c < Teams; ++c) delete Team[c];
464 delete [] Team;
465 Team = 0;
467 delete GameScript;
468 GameScript = 0;
469 msgsystem::Format();
470 DangerMap.clear();
474 void game::Run () {
475 for (;;) {
476 if (!InWilderness) {
477 /* Temporary places */
478 static int Counter = 0;
479 if (++Counter == 10) {
480 CurrentLevel->GenerateMonsters();
481 Counter = 0;
483 if (CurrentDungeonIndex == ELPURI_CAVE && CurrentLevelIndex == ZOMBIE_LEVEL && !RAND_N(1000+NecroCounter)) {
484 character *Char = necromancer::Spawn(RAND_N(4) ? APPRENTICE_NECROMANCER : MASTER_NECROMANCER);
485 v2 Pos;
486 for (int c2 = 0; c2 < 30; ++c2) {
487 Pos = GetCurrentLevel()->GetRandomSquare(Char);
488 if (abs(int(Pos.X)-Player->GetPos().X) > 20 || abs(int(Pos.Y)-Player->GetPos().Y) > 20) break;
490 if (Pos != ERROR_V2) {
491 Char->SetTeam(GetTeam(MONSTER_TEAM));
492 Char->PutTo(Pos);
493 Char->SetGenerationDanger(GetCurrentLevel()->GetDifficulty());
494 Char->SignalGeneration();
495 Char->SignalNaturalGeneration();
496 ivantime Time;
497 GetTime(Time);
498 int Modifier = Time.Day - EDIT_ATTRIBUTE_DAY_MIN;
499 if (Modifier > 0) Char->EditAllAttributes(Modifier >> EDIT_ATTRIBUTE_DAY_SHIFT);
500 NecroCounter += 50;
501 } else {
502 delete Char;
503 //Char->SendToHell(); // k8:equipment
507 if (!(GetTick() % 1000)) CurrentLevel->CheckSunLight();
509 if ((CurrentDungeonIndex == NEW_ATTNAM || CurrentDungeonIndex == ATTNAM) && CurrentLevelIndex == 0) {
510 sLong OldVolume = GlobalRainLiquid->GetVolume();
511 sLong NewVolume = Max(sLong(sin((Tick+GlobalRainTimeModifier)*0.0003)*300-150), 0);
512 if (NewVolume && !OldVolume) CurrentLevel->EnableGlobalRain();
513 else if(!NewVolume && OldVolume) CurrentLevel->DisableGlobalRain();
514 GlobalRainLiquid->SetVolumeNoSignals(NewVolume);
517 item *Item;
518 if (!RAND_N(2)) Item = wand::Spawn(1 + RAND_N(12));
519 else if(!RAND_N(2)) {
520 Item = beartrap::Spawn();
521 Item->SetIsActive(true);
522 Item->SetTeam(MONSTER_TEAM);
523 } else if(!RAND_N(2)) {
524 Item = mine::Spawn();
525 Item->SetIsActive(true);
526 Item->SetTeam(MONSTER_TEAM);
527 } else Item = holybanana::Spawn();
528 CurrentLevel->GetLSquare(CurrentLevel->GetRandomSquare())->AddItem(Item);
531 if(!RAND_N(10)) {
532 character *Char = protosystem::CreateMonster(0, 1000000);
533 Char->ChangeTeam(GetTeam(RAND() % Teams));
534 Char->PutTo(CurrentLevel->GetRandomSquare(Char));
537 if (!RAND_N(5)) {
538 character *Char;
539 if (!RAND_N(5)) Char = spider::Spawn(GIANT);
540 else if (!RAND_N(5)) Char = darkmage::Spawn(1 + RAND_N(4));
541 else if (!RAND_N(5)) Char = necromancer::Spawn(1 + RAND_N(2));
542 else if (!RAND_N(5)) Char = chameleon::Spawn();
543 else if (!RAND_N(5)) Char = kamikazedwarf::Spawn(1 + RAND_N(GODS));
544 else if (!RAND_N(5)) Char = mommo::Spawn(1 + RAND_N(2));
545 else if (!RAND_N(3)) Char = bunny::Spawn(RAND_2 ? ADULT_MALE : ADULT_FEMALE);
546 else if (!RAND_N(3)) Char = eddy::Spawn();
547 else if (!RAND_N(3)) Char = magicmushroom::Spawn();
548 else if (!RAND_N(5)) Char = mushroom::Spawn();
549 else if (!RAND_N(3)) Char = blinkdog::Spawn();
550 else if (!RAND_N(5)) Char = tourist::Spawn(1 + RAND_N(3));
551 else if (!RAND_N(5)) Char = hattifattener::Spawn();
552 else if (!RAND_N(5)) Char = genetrixvesana::Spawn();
553 else if (!RAND_N(5)) Char = skunk::Spawn();
554 else if (!RAND_N(5)) Char = ennerbeast::Spawn();
555 else if (!RAND_N(5)) Char = werewolfhuman::Spawn();
556 else if (!RAND_N(5)) Char = unicorn::Spawn(1 + RAND_N(3));
557 else if (!RAND_N(5)) Char = floatingeye::Spawn();
558 else if (!RAND_N(5)) Char = zombie::Spawn();
559 else if (!RAND_N(5)) Char = magpie::Spawn();
560 else if (!RAND_N(5)) Char = elpuri::Spawn();
561 else if (!RAND_N(5)) Char = vladimir::Spawn();
562 else if (!RAND_N(5)) Char = billswill::Spawn();
563 else if (!RAND_N(5)) Char = ghost::Spawn();
564 else if (!RAND_N(5)) Char = dolphin::Spawn();
565 else if (!RAND_N(5)) Char = cossack::Spawn();
566 else Char = invisiblestalker::Spawn();
567 Char->SetTeam(GetTeam(RAND() % Teams));
568 Char->PutTo(CurrentLevel->GetRandomSquare(Char));
574 try {
575 pool::Be();
576 pool::BurnHell();
577 IncreaseTick();
578 ApplyDivineTick();
579 } catch (quitrequest) {
580 break;
581 } catch (areachangerequest) {
587 void game::InitLuxTable () {
588 if (!LuxTable) {
589 Alloc3D(LuxTable, 256, 33, 33);
590 for (int c = 0; c < 0x100; ++c)
591 for (int x = 0; x < 33; ++x)
592 for (int y = 0; y < 33; ++y) {
593 int X = x-16, Y = y-16;
594 LuxTable[c][x][y] = int(c/(double(X*X+Y*Y)/128+1));
596 atexit(DeInitLuxTable);
601 void game::DeInitLuxTable () {
602 delete [] LuxTable;
603 LuxTable = 0;
607 void game::UpdateCameraX () {
608 UpdateCameraX(Player->GetPos().X);
612 void game::UpdateCameraY () {
613 UpdateCameraY(Player->GetPos().Y);
617 void game::UpdateCameraX (int X) {
618 UpdateCameraCoordinate(Camera.X, X, GetCurrentArea()->GetXSize(), GetScreenXSize());
622 void game::UpdateCameraY (int Y) {
623 UpdateCameraCoordinate(Camera.Y, Y, GetCurrentArea()->GetYSize(), GetScreenYSize());
627 void game::UpdateCameraCoordinate (int &Coordinate, int Center, int Size, int ScreenSize) {
628 int OldCoordinate = Coordinate;
629 if (Size < ScreenSize) Coordinate = (Size-ScreenSize)>>1;
630 else if(Center < ScreenSize>>1) Coordinate = 0;
631 else if(Center > Size-(ScreenSize>>1)) Coordinate = Size-ScreenSize;
632 else Coordinate = Center-(ScreenSize>>1);
633 if (Coordinate != OldCoordinate) GetCurrentArea()->SendNewDrawRequest();
637 cchar *game::Insult () {
638 static const char *insults[19] = {
639 "moron",
640 "silly",
641 "idiot",
642 "airhead",
643 "jerk",
644 "dork",
645 "Mr. Mole",
646 "navastater",
647 "potatoes-for-eyes",
648 "lamer",
649 "mommo-for-brains",
650 "pinhead",
651 "stupid-headed person",
652 "software abuser",
653 "loser",
654 "peaballs",
655 "person-with-problems",
656 "unimportant user",
657 "hugger-mugger"
659 int n = RAND_N(18);
660 if (n < 0 || n > 18) n = 18;
661 return insults[n];
665 /* DefaultAnswer = REQUIRES_ANSWER the question requires an answer */
666 truth game::TruthQuestion (cfestring &String, int DefaultAnswer, int OtherKeyForTrue) {
667 if (DefaultAnswer == NO) DefaultAnswer = 'n';
668 else if (DefaultAnswer == YES) DefaultAnswer = 'y';
669 else if (DefaultAnswer != REQUIRES_ANSWER) ABORT("Illegal TruthQuestion DefaultAnswer send!");
670 int FromKeyQuestion = KeyQuestion(String, DefaultAnswer, 9, 'y', 'Y', 'n', 'N', 't', 'T', 'o', 'O', OtherKeyForTrue);
671 return
672 FromKeyQuestion == 'y' || FromKeyQuestion == 'Y' ||
673 FromKeyQuestion == 't' || FromKeyQuestion == 'T' ||
674 FromKeyQuestion == OtherKeyForTrue;
678 void game::DrawEverything () {
679 DrawEverythingNoBlit();
680 graphics::BlitDBToScreen();
684 truth game::OnScreen (v2 Pos) {
685 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();
689 void game::DrawEverythingNoBlit (truth AnimationDraw) {
690 if (LOSUpdateRequested && Player->IsEnabled()) {
691 if (!IsInWilderness()) GetCurrentLevel()->UpdateLOS(); else GetWorldMap()->UpdateLOS();
694 if (OnScreen(CursorPos)) {
695 if (!IsInWilderness() || CurrentWSquareMap[CursorPos.X][CursorPos.Y]->GetLastSeen() || GetSeeWholeMapCheatMode())
696 CurrentArea->GetSquare(CursorPos)->SendStrongNewDrawRequest();
697 else
698 DOUBLE_BUFFER->Fill(CalculateScreenCoordinates(CursorPos), TILE_V2, 0);
701 for (unsigned int c = 0; c < SpecialCursorPos.size(); ++c) {
702 if (OnScreen(SpecialCursorPos[c])) CurrentArea->GetSquare(SpecialCursorPos[c])->SendStrongNewDrawRequest();
705 globalwindowhandler::UpdateTick();
706 GetCurrentArea()->Draw(AnimationDraw);
707 Player->DrawPanel(AnimationDraw);
709 if (!AnimationDraw) msgsystem::Draw();
711 if (OnScreen(CursorPos)) {
712 v2 ScreenCoord = CalculateScreenCoordinates(CursorPos);
713 blitdata B = {
714 DOUBLE_BUFFER,
715 { 0, 0 },
716 { ScreenCoord.X, ScreenCoord.Y },
717 { TILE_SIZE, TILE_SIZE },
718 { 0 },
719 TRANSPARENT_COLOR,
720 ALLOW_ANIMATE|ALLOW_ALPHA
723 if (!IsInWilderness() && !GetSeeWholeMapCheatMode()) {
724 lsquare *Square = CurrentLSquareMap[CursorPos.X][CursorPos.Y];
725 if (Square->GetLastSeen() != GetLOSTick()) Square->DrawMemorized(B);
728 if (DoZoom()) {
729 B.Src = B.Dest;
730 B.Dest.X = RES.X - 96;
731 B.Dest.Y = RES.Y - 96;
732 B.Stretch = 5;
733 DOUBLE_BUFFER->StretchBlit(B);
736 igraph::DrawCursor(ScreenCoord, CursorData);
739 if (Player->IsEnabled()) {
740 if (Player->IsSmall()) {
741 v2 Pos = Player->GetPos();
742 if (OnScreen(Pos)) {
743 v2 ScreenCoord = CalculateScreenCoordinates(Pos);
744 igraph::DrawCursor(ScreenCoord, Player->GetCursorData());
746 } else {
747 for (int f = 0; f < Player->GetSquaresUnder(); ++f) {
748 v2 Pos = Player->GetPos(f);
749 if (OnScreen(Pos)) {
750 v2 ScreenCoord = CalculateScreenCoordinates(Pos);
751 igraph::DrawCursor(ScreenCoord, Player->GetCursorData()|CURSOR_BIG, f);
757 for (unsigned int c = 0; c < SpecialCursorPos.size(); ++c) {
758 if (OnScreen(SpecialCursorPos[c])) {
759 v2 ScreenCoord = CalculateScreenCoordinates(SpecialCursorPos[c]);
760 igraph::DrawCursor(ScreenCoord, SpecialCursorData[c]);
761 GetCurrentArea()->GetSquare(SpecialCursorPos[c])->SendStrongNewDrawRequest();
767 truth game::Save (cfestring &SaveName) {
768 if (!GetCurrentArea()->GetSquare(Player->GetPos())->GetCharacter()) {
769 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);
770 return false;
772 DrawEverythingNoBlit();
773 #if defined(SGAME_SHOTS_IPU) || !defined(HAVE_IMLIB2)
774 DOUBLE_BUFFER->SaveScaledIPU(SaveName+".ipu", 0.8); //640; 320
775 #else
776 DOUBLE_BUFFER->SaveScaledPNG(SaveName+".png", 0.8); //640; 320
777 #endif
778 outputfile SaveFile(SaveName+".sav", ivanconfig::GetUseMaximumCompression());
779 SaveFile << int(SAVE_FILE_VERSION);
780 SaveFile << GameScript << CurrentDungeonIndex << CurrentLevelIndex << Camera;
781 SaveFile << WizardMode << SeeWholeMapCheatMode << GoThroughWallsCheat;
782 SaveFile << Tick << Turn << InWilderness << NextCharacterID << NextItemID << NextTrapID << NecroCounter;
783 SaveFile << SumoWrestling << PlayerSumoChampion << GlobalRainTimeModifier;
784 SaveFile << PlayerSolicitusChampion;
785 sLong Seed = RAND();
786 femath::SetSeed(Seed);
787 SaveFile << Seed;
788 SaveFile << AveragePlayerArmStrengthExperience;
789 SaveFile << AveragePlayerLegStrengthExperience;
790 SaveFile << AveragePlayerDexterityExperience;
791 SaveFile << AveragePlayerAgilityExperience;
792 SaveFile << Teams << Dungeons << StoryState << PlayerRunning;
793 SaveFile << MondedrPass << RingOfThieves << Masamune << Muramasa << LoricatusHammer << Liberator;
794 SaveFile << OmmelBloodMission << RegiiTalkState;
795 SaveFile << PlayerMassacreMap << PetMassacreMap << MiscMassacreMap;
796 SaveFile << PlayerMassacreAmount << PetMassacreAmount << MiscMassacreAmount;
797 SaveArray(SaveFile, EquipmentMemory, MAX_EQUIPMENT_SLOTS);
798 int c;
799 for (c = 0; c < ATTRIBUTES; ++c) SaveFile << OldAttribute[c] << NewAttribute[c] << LastAttributeChangeTick[c];
800 for (c = 1; c < Dungeons; ++c) SaveFile << Dungeon[c];
801 for (c = 1; c <= GODS; ++c) SaveFile << God[c];
802 for (c = 0; c < Teams; ++c) SaveFile << Team[c];
803 if (InWilderness) {
804 SaveWorldMap(SaveName, false);
805 } else {
806 GetCurrentDungeon()->SaveLevel(SaveName, CurrentLevelIndex, false);
808 SaveFile << Player->GetPos() << PlayerName;
809 msgsystem::Save(SaveFile);
810 SaveFile << DangerMap << NextDangerIDType << NextDangerIDConfigIndex;
811 SaveFile << DefaultPolymorphTo << DefaultSummonMonster;
812 SaveFile << DefaultWish << DefaultChangeMaterial << DefaultDetectMaterial << DefaultTeam;
813 SaveFile << GetTimeSpent();
814 /* or in more readable format: time() - LastLoad + TimeAtLastLoad */
815 SaveFile << PlayerHasReceivedAllGodsKnownBonus;
816 protosystem::SaveCharacterDataBaseFlags(SaveFile);
817 return true;
821 int game::Load (cfestring &SaveName) {
822 inputfile SaveFile(SaveName+".sav", 0, false);
823 if (!SaveFile.IsOpen()) return NEW_GAME;
824 int Version;
825 SaveFile >> Version;
826 if (Version != SAVE_FILE_VERSION) {
827 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)) {
828 return NEW_GAME;
829 } else {
830 return BACK;
833 SaveFile >> GameScript >> CurrentDungeonIndex >> CurrentLevelIndex >> Camera;
834 SaveFile >> WizardMode >> SeeWholeMapCheatMode >> GoThroughWallsCheat;
835 SaveFile >> Tick >> Turn >> InWilderness >> NextCharacterID >> NextItemID >> NextTrapID >> NecroCounter;
836 SaveFile >> SumoWrestling >> PlayerSumoChampion >> GlobalRainTimeModifier;
837 SaveFile >> PlayerSolicitusChampion;
838 femath::SetSeed(ReadType(sLong, SaveFile));
839 SaveFile >> AveragePlayerArmStrengthExperience;
840 SaveFile >> AveragePlayerLegStrengthExperience;
841 SaveFile >> AveragePlayerDexterityExperience;
842 SaveFile >> AveragePlayerAgilityExperience;
843 SaveFile >> Teams >> Dungeons >> StoryState >> PlayerRunning;
844 SaveFile >> MondedrPass >> RingOfThieves >> Masamune >> Muramasa >> LoricatusHammer >> Liberator;
845 SaveFile >> OmmelBloodMission >> RegiiTalkState;
846 SaveFile >> PlayerMassacreMap >> PetMassacreMap >> MiscMassacreMap;
847 SaveFile >> PlayerMassacreAmount >> PetMassacreAmount >> MiscMassacreAmount;
848 LoadArray(SaveFile, EquipmentMemory, MAX_EQUIPMENT_SLOTS);
849 for (int c = 0; c < ATTRIBUTES; ++c) SaveFile >> OldAttribute[c] >> NewAttribute[c] >> LastAttributeChangeTick[c];
850 Dungeon = new dungeon*[Dungeons];
851 Dungeon[0] = 0;
852 for (int c = 1; c < Dungeons; ++c) SaveFile >> Dungeon[c];
853 God = new god*[GODS+1];
854 God[0] = 0;
855 for (int c = 1; c <= GODS; ++c) SaveFile >> God[c];
856 Team = new team*[Teams];
857 for (int c = 0; c < Teams; ++c) SaveFile >> Team[c];
858 if (InWilderness) {
859 SetCurrentArea(LoadWorldMap(SaveName));
860 CurrentWSquareMap = WorldMap->GetMap();
861 igraph::CreateBackGround(GRAY_FRACTAL);
862 } else {
863 SetCurrentArea(CurrentLevel = GetCurrentDungeon()->LoadLevel(SaveName, CurrentLevelIndex));
864 CurrentLSquareMap = CurrentLevel->GetMap();
865 igraph::CreateBackGround(*CurrentLevel->GetLevelScript()->GetBackGroundType());
867 v2 Pos;
868 SaveFile >> Pos >> PlayerName;
869 SetPlayer(GetCurrentArea()->GetSquare(Pos)->GetCharacter());
870 if (!PLAYER) {
871 DeInit();
872 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)) {
873 return NEW_GAME;
874 } else {
875 return BACK;
878 msgsystem::Load(SaveFile);
879 SaveFile >> DangerMap >> NextDangerIDType >> NextDangerIDConfigIndex;
880 SaveFile >> DefaultPolymorphTo >> DefaultSummonMonster;
881 SaveFile >> DefaultWish >> DefaultChangeMaterial >> DefaultDetectMaterial >> DefaultTeam;
882 SaveFile >> TimePlayedBeforeLastLoad;
883 SaveFile >> PlayerHasReceivedAllGodsKnownBonus;
884 LastLoad = time(0);
885 protosystem::LoadCharacterDataBaseFlags(SaveFile);
886 return LOADED;
890 festring game::SaveName (cfestring &Base) {
891 festring SaveName = GetSavePath();
892 if (!Base.GetSize()) SaveName << PlayerName; else SaveName << Base;
893 for (festring::sizetype c = 0; c < SaveName.GetSize(); ++c) if (SaveName[c] == ' ') SaveName[c] = '_';
894 return SaveName;
898 int game::GetMoveCommandKeyBetweenPoints (v2 A, v2 B) {
899 for (int c = 0; c < EXTENDED_DIRECTION_COMMAND_KEYS; ++c) {
900 if ((A + GetMoveVector(c)) == B) return GetMoveCommandKey(c);
902 return DIR_ERROR;
906 void game::ApplyDivineTick () {
907 for (int c = 1; c <= GODS; ++c) GetGod(c)->ApplyDivineTick();
911 void game::ApplyDivineAlignmentBonuses (god *CompareTarget, int Multiplier, truth Good) {
912 for (int c = 1; c <= GODS; ++c) if (GetGod(c) != CompareTarget) GetGod(c)->AdjustRelation(CompareTarget, Multiplier, Good);
916 v2 game::GetDirectionVectorForKey (int Key) {
917 if (Key == KEY_NUMPAD_5 || Key == '.') return ZERO_V2; /* k8: '.' */
918 for (int c = 0; c < EXTENDED_DIRECTION_COMMAND_KEYS; ++c) if (Key == GetMoveCommandKey(c)) return GetMoveVector(c);
919 return ERROR_V2;
923 double game::GetMinDifficulty () {
924 double Base = CurrentLevel->GetDifficulty()*0.2;
925 sLong MultiplierExponent = 0;
926 ivantime Time;
927 GetTime(Time);
928 int Modifier = Time.Day-DANGER_PLUS_DAY_MIN;
929 if (Modifier > 0) Base += DANGER_PLUS_MULTIPLIER * Modifier;
930 for (;;) {
931 int Dice = RAND()%25;
932 if (Dice < 5 && MultiplierExponent > -3) {
933 Base /= 3;
934 --MultiplierExponent;
935 continue;
937 if (Dice >= 20 && MultiplierExponent < 3) {
938 Base *= 3;
939 ++MultiplierExponent;
940 continue;
942 return Base;
947 void game::ShowLevelMessage () {
948 if (CurrentLevel->GetLevelMessage().GetSize()) ADD_MESSAGE("%s", CurrentLevel->GetLevelMessage().CStr());
949 CurrentLevel->SetLevelMessage("");
953 int game::DirectionQuestion (cfestring &Topic, truth RequireAnswer, truth AcceptYourself) {
954 for (;;) {
955 int Key = AskForKeyPress(Topic);
956 if (AcceptYourself && (Key == '.' || Key == KEY_NUMPAD_5)) return YOURSELF; //k8
957 for (int c = 0; c < DIRECTION_COMMAND_KEYS; ++c) if (Key == GetMoveCommandKey(c)) return c;
958 if (!RequireAnswer) return DIR_ERROR;
963 void game::RemoveSaves (truth RealSavesAlso) {
964 if (RealSavesAlso) {
965 remove(festring(SaveName()+".sav").CStr());
966 remove(festring(SaveName()+".wm").CStr());
967 remove(festring(SaveName()+".png").CStr());
968 remove(festring(SaveName()+".ipu").CStr());
970 remove(festring(AutoSaveFileName+".sav").CStr());
971 remove(festring(AutoSaveFileName+".wm").CStr());
972 remove(festring(AutoSaveFileName+".png").CStr());
973 remove(festring(AutoSaveFileName+".ipu").CStr());
974 festring File;
975 for (int i = 1; i < Dungeons; ++i) {
976 for (int c = 0; c < GetDungeon(i)->GetLevels(); ++c) {
977 /* This looks very odd. And it is very odd.
978 * Indeed, gcc is very odd to not compile this correctly with -O3
979 * if it is written in a less odd way. */
980 File = SaveName()+'.'+i;
981 File << c;
982 if (RealSavesAlso) remove(File.CStr());
983 File = AutoSaveFileName+'.'+i;
984 File << c;
985 remove(File.CStr());
991 void game::SetPlayer (character *NP) {
992 Player = NP;
993 if (Player) Player->AddFlags(C_PLAYER);
997 void game::InitDungeons () {
998 Dungeons = *GetGameScript()->GetDungeons()+1;
999 //fprintf(stderr, "dungeon count: %d\n", Dungeons);
1000 Dungeon = new dungeon *[Dungeons];
1001 Dungeon[0] = 0;
1002 for (int c = 1; c < Dungeons; ++c) {
1003 Dungeon[c] = new dungeon(c);
1004 Dungeon[c]->SetIndex(c);
1009 void game::DoEvilDeed (int Amount) {
1010 if (!Amount) return;
1011 for (int c = 1; c <= GODS; ++c) {
1012 int Change = Amount-Amount*GetGod(c)->GetAlignment()/5;
1013 if (!IsInWilderness() && Player->GetLSquareUnder()->GetDivineMaster() == c) {
1014 if (GetGod(c)->GetRelation()-(Change << 1) < -750) {
1015 if (GetGod(c)->GetRelation() > -750) GetGod(c)->SetRelation(-750);
1016 } else if (GetGod(c)->GetRelation()-(Change << 1) > 750) {
1017 if (GetGod(c)->GetRelation() < 750) GetGod(c)->SetRelation(750);
1018 } else GetGod(c)->SetRelation(GetGod(c)->GetRelation()-(Change << 1));
1019 } else {
1020 if(GetGod(c)->GetRelation()-Change < -500) {
1021 if (GetGod(c)->GetRelation() > -500) GetGod(c)->SetRelation(-500);
1022 } else if (GetGod(c)->GetRelation()-Change > 500) {
1023 if (GetGod(c)->GetRelation() < 500) GetGod(c)->SetRelation(500);
1024 } else GetGod(c)->SetRelation(GetGod(c)->GetRelation() - Change);
1030 void game::SaveWorldMap (cfestring &SaveName, truth DeleteAfterwards) {
1031 outputfile SaveFile(SaveName+".wm", ivanconfig::GetUseMaximumCompression());
1032 SaveFile << WorldMap;
1033 if (DeleteAfterwards) {
1034 delete WorldMap;
1035 WorldMap = 0;
1040 worldmap *game::LoadWorldMap (cfestring &SaveName) {
1041 inputfile SaveFile(SaveName+".wm");
1042 SaveFile >> WorldMap;
1043 return WorldMap;
1047 void game::Hostility (team *Attacker, team *Defender) {
1048 for (int c = 0; c < Teams; ++c) {
1049 if (GetTeam(c) != Attacker && GetTeam(c) != Defender &&
1050 GetTeam(c)->GetRelation(Defender) == FRIEND &&
1051 c != NEW_ATTNAM_TEAM && c != TOURIST_GUIDE_TEAM) // gum solution
1052 GetTeam(c)->SetRelation(Attacker, HOSTILE);
1057 void game::CreateTeams () {
1058 Teams = *GetGameScript()->GetTeams();
1059 //fprintf(stderr, "team count: %d\n", Teams);
1060 Team = new team*[Teams];
1061 for (int c = 0; c < Teams; ++c) {
1062 Team[c] = new team(c);
1063 for (int i = 0; i < c; ++i) Team[i]->SetRelation(Team[c], UNCARING);
1065 for (int c = 0; c < Teams; ++c) if (c != MONSTER_TEAM) Team[MONSTER_TEAM]->SetRelation(Team[c], HOSTILE);
1066 const std::list<std::pair<int, teamscript> >& TeamScript = GetGameScript()->GetTeam();
1067 for (std::list<std::pair<int, teamscript> >::const_iterator i = TeamScript.begin(); i != TeamScript.end(); ++i) {
1068 for (uInt c = 0; c < i->second.GetRelation().size(); ++c) {
1069 GetTeam(i->second.GetRelation()[c].first)->SetRelation(GetTeam(i->first), i->second.GetRelation()[c].second);
1071 cint *KillEvilness = i->second.GetKillEvilness();
1072 if (KillEvilness) GetTeam(i->first)->SetKillEvilness(*KillEvilness);
1073 if (i->second.GetName()) GetTeam(i->first)->SetName(*i->second.GetName());
1078 team *game::FindTeam (cfestring &name) {
1079 for (int c = 0; c < Teams; ++c) {
1080 if (Team[c]->GetName().CompareIgnoreCase(name) == 0) return Team[c];
1082 return 0;
1086 /* v2 Pos should be removed from xxxQuestion()s? */
1087 festring game::StringQuestion (cfestring &Topic, col16 Color, festring::sizetype MinLetters, festring::sizetype MaxLetters, truth AllowExit, stringkeyhandler KeyHandler) {
1088 DrawEverythingNoBlit();
1089 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23)); // pos may be incorrect!
1090 festring Return;
1091 iosystem::StringQuestion(Return, Topic, v2(16, 6), Color, MinLetters, MaxLetters, false, AllowExit, KeyHandler);
1092 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1093 return Return;
1097 sLong game::NumberQuestion (cfestring &Topic, col16 Color, truth ReturnZeroOnEsc) {
1098 DrawEverythingNoBlit();
1099 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1100 sLong Return = iosystem::NumberQuestion(Topic, v2(16, 6), Color, false, ReturnZeroOnEsc);
1101 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1102 return Return;
1106 sLong game::ScrollBarQuestion (cfestring &Topic, sLong BeginValue, sLong Step, sLong Min, sLong Max, sLong AbortValue, col16 TopicColor, col16 Color1, col16 Color2, void (*Handler)(sLong)) {
1107 DrawEverythingNoBlit();
1108 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1109 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);
1110 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1111 return Return;
1115 feuLong game::IncreaseLOSTick () {
1116 if (LOSTick != 0xFE) return LOSTick += 2;
1117 CurrentLevel->InitLastSeen();
1118 return LOSTick = 4;
1122 void game::UpdateCamera () {
1123 UpdateCameraX();
1124 UpdateCameraY();
1128 truth game::HandleQuitMessage () {
1129 if (IsRunning()) {
1130 if (IsInGetCommand()) {
1131 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)) {
1132 case 0:
1133 Save();
1134 RemoveSaves(false);
1135 break;
1136 case 2:
1137 GetCurrentArea()->SendNewDrawRequest();
1138 DrawEverything();
1139 return false;
1140 default:
1141 festring Msg = CONST_S("cowardly quit the game");
1142 Player->AddScoreEntry(Msg, 0.75);
1143 End(Msg, true, false);
1144 break;
1146 } 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)) {
1147 RemoveSaves();
1148 } else {
1149 GetCurrentArea()->SendNewDrawRequest();
1150 DrawEverything();
1151 return false;
1154 return true;
1158 int game::GetDirectionForVector (v2 Vector) {
1159 for (int c = 0; c < DIRECTION_COMMAND_KEYS; ++c) if (Vector == GetMoveVector(c)) return c;
1160 return DIR_ERROR;
1164 cchar *game::GetVerbalPlayerAlignment () {
1165 sLong Sum = 0;
1166 for (int c = 1; c <= GODS; ++c) {
1167 if (GetGod(c)->GetRelation() > 0) Sum += GetGod(c)->GetRelation() * (5 - GetGod(c)->GetAlignment());
1169 if (Sum > 15000) return "extremely lawful";
1170 if (Sum > 10000) return "very lawful";
1171 if (Sum > 5000) return "lawful";
1172 if (Sum > 1000) return "mildly lawful";
1173 if (Sum > -1000) return "neutral";
1174 if (Sum > -5000) return "mildly chaotic";
1175 if (Sum > -10000) return "chaotic";
1176 if (Sum > -15000) return "very chaotic";
1177 return "extremely chaotic";
1181 void game::CreateGods () {
1182 God = new god*[GODS+1];
1183 God[0] = 0;
1184 for (int c = 1; c < protocontainer<god>::GetSize(); ++c) God[c] = protocontainer<god>::GetProto(c)->Spawn();
1188 void game::BusyAnimation () {
1189 BusyAnimation(DOUBLE_BUFFER, false);
1193 void game::BusyAnimation (bitmap *Buffer, truth ForceDraw) {
1194 static clock_t LastTime = 0;
1195 static int Frame = 0;
1196 static blitdata B1 = {
1198 { 0, 0 },
1199 { 0, 0 },
1200 { RES.X, RES.Y },
1201 { 0 },
1205 static blitdata B2 = {
1207 { 0, 0 },
1208 { (RES.X >> 1) - 100, (RES.Y << 1) / 3 - 100 },
1209 { 200, 200 },
1210 { 0 },
1214 if (ForceDraw || clock()-LastTime > CLOCKS_PER_SEC/25) {
1215 B2.Bitmap = Buffer;
1216 B2.Dest.X = (RES.X>>1)-100+EnterTextDisplacement.X;
1217 B2.Dest.Y = (RES.Y<<1)/3-100+EnterTextDisplacement.Y;
1218 if (EnterImage) {
1219 B1.Bitmap = Buffer;
1220 EnterImage->NormalMaskedBlit(B1);
1222 BusyAnimationCache[Frame]->NormalBlit(B2);
1223 if (Buffer == DOUBLE_BUFFER) graphics::BlitDBToScreen();
1224 if (++Frame == 32) Frame = 0;
1225 LastTime = clock();
1230 void game::CreateBusyAnimationCache () {
1231 bitmap Elpuri(TILE_V2, TRANSPARENT_COLOR);
1232 Elpuri.ActivateFastFlag();
1233 packcol16 Color = MakeRGB16(60, 60, 60);
1234 igraph::GetCharacterRawGraphic()->MaskedBlit(&Elpuri, v2(64, 0), ZERO_V2, TILE_V2, &Color);
1235 bitmap Circle(v2(200, 200), TRANSPARENT_COLOR);
1236 Circle.ActivateFastFlag();
1237 for (int x = 0; x < 4; ++x) Circle.DrawPolygon(100, 100, 95+x, 50, MakeRGB16(255-12*x, 0, 0));
1238 blitdata B1 = {
1240 { 0, 0 },
1241 { 92, 92 },
1242 { TILE_SIZE, TILE_SIZE },
1243 { 0 },
1244 TRANSPARENT_COLOR,
1247 blitdata B2 = {
1249 { 0, 0 },
1250 { 0, 0 },
1251 { 200, 200 },
1252 { 0 },
1253 TRANSPARENT_COLOR,
1256 for (int c = 0; c < 32; ++c) {
1257 B1.Bitmap = B2.Bitmap = BusyAnimationCache[c] = new bitmap(v2(200, 200), 0);
1258 B1.Bitmap->ActivateFastFlag();
1259 Elpuri.NormalMaskedBlit(B1);
1260 double Rotation = 0.3+c*FPI/80;
1261 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);
1262 Circle.NormalMaskedBlit(B2);
1267 int game::AskForKeyPress (cfestring &Topic) {
1268 DrawEverythingNoBlit();
1269 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Topic.CapitalizeCopy().CStr());
1270 graphics::BlitDBToScreen();
1271 int Key = GET_KEY();
1272 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1273 return Key;
1277 void game::AskForEscPress (cfestring &Topic) {
1278 DrawEverythingNoBlit();
1279 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), RED/*WHITE*/, "%s [press ESC]", Topic.CapitalizeCopy().CStr());
1280 graphics::BlitDBToScreen();
1281 int Key;
1282 do {
1283 Key = GET_KEY();
1284 } while (Key != KEY_ESC);
1285 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1289 /* Handler is called when the key has been identified as a movement key
1290 * KeyHandler is called when the key has NOT been identified as a movement key
1291 * Both can be deactivated by passing 0 as parameter */
1292 v2 game::PositionQuestion (cfestring &Topic, v2 CursorPos, void (*Handler)(v2), positionkeyhandler KeyHandler, truth Zoom) {
1293 int Key = 0;
1294 SetDoZoom(Zoom);
1295 v2 Return;
1296 CursorData = RED_CURSOR;
1297 if (Handler) Handler(CursorPos);
1298 for (;;) {
1299 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1300 if (!Square->HasBeenSeen() &&
1301 (!Square->GetCharacter() || !Square->GetCharacter()->CanBeSeenByPlayer()) &&
1302 !GetSeeWholeMapCheatMode()) DOUBLE_BUFFER->Fill(CalculateScreenCoordinates(CursorPos), TILE_V2, BLACK);
1303 else GetCurrentArea()->GetSquare(CursorPos)->SendStrongNewDrawRequest();
1305 if (Key == ' ' || Key == '.' || Key == KEY_NUMPAD_5) { Return = CursorPos; break; }
1306 if (Key == KEY_ESC) { Return = ERROR_V2; break; }
1308 v2 DirectionVector = GetDirectionVectorForKey(Key);
1309 if (DirectionVector != ERROR_V2) {
1310 CursorPos += DirectionVector;
1311 if (CursorPos.X > GetCurrentArea()->GetXSize()-1) CursorPos.X = 0;
1312 if (CursorPos.X < 0) CursorPos.X = GetCurrentArea()->GetXSize()-1;
1313 if (CursorPos.Y > GetCurrentArea()->GetYSize()-1) CursorPos.Y = 0;
1314 if (CursorPos.Y < 0) CursorPos.Y = GetCurrentArea()->GetYSize()-1;
1315 if (Handler) Handler(CursorPos);
1316 } else if (KeyHandler) {
1317 CursorPos = KeyHandler(CursorPos, Key);
1318 if (CursorPos == ERROR_V2 || CursorPos == ABORT_V2) {
1319 Return = CursorPos;
1320 break;
1324 if (ivanconfig::GetAutoCenterMapOnLook()) {
1325 UpdateCameraX(CursorPos.X);
1326 UpdateCameraY(CursorPos.Y);
1327 } else {
1328 if (CursorPos.X < GetCamera().X+3 || CursorPos.X >= GetCamera().X+GetScreenXSize()-3) UpdateCameraX(CursorPos.X);
1329 if (CursorPos.Y < GetCamera().Y+3 || CursorPos.Y >= GetCamera().Y+GetScreenYSize()-3) UpdateCameraY(CursorPos.Y);
1332 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Topic.CStr());
1333 SetCursorPos(CursorPos);
1334 DrawEverything();
1335 Key = GET_KEY();
1338 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1339 igraph::BlitBackGround(v2(RES.X-96, RES.Y-96), v2(80, 80));
1340 SetDoZoom(false);
1341 SetCursorPos(v2(-1, -1));
1342 return Return;
1346 void game::LookHandler (v2 CursorPos) {
1347 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1348 festring OldMemory;
1350 if (GetSeeWholeMapCheatMode()) {
1351 OldMemory = Square->GetMemorizedDescription();
1352 if (IsInWilderness()) GetWorldMap()->GetWSquare(CursorPos)->UpdateMemorizedDescription(true);
1353 else GetCurrentLevel()->GetLSquare(CursorPos)->UpdateMemorizedDescription(true);
1356 festring Msg;
1357 if (Square->HasBeenSeen() || GetSeeWholeMapCheatMode()) {
1358 if (!IsInWilderness() && !Square->CanBeSeenByPlayer() && GetCurrentLevel()->GetLSquare(CursorPos)->CanBeFeltByPlayer())
1359 Msg = CONST_S("You feel here ");
1360 else if (Square->CanBeSeenByPlayer(true) || GetSeeWholeMapCheatMode())
1361 Msg = CONST_S("You see here ");
1362 else
1363 Msg = CONST_S("You remember here ");
1364 Msg << Square->GetMemorizedDescription() << '.';
1365 if (!IsInWilderness() && (Square->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) {
1366 lsquare *LSquare = GetCurrentLevel()->GetLSquare(CursorPos);
1367 LSquare->DisplaySmokeInfo(Msg);
1368 if (LSquare->HasEngravings() && LSquare->IsTransparent()) {
1369 if (LSquare->EngravingsCanBeReadByPlayer() || GetSeeWholeMapCheatMode()) LSquare->DisplayEngravedInfo(Msg);
1370 else Msg << " Something has been engraved here.";
1373 } else Msg = CONST_S("You have never been here.");
1374 character *Character = Square->GetCharacter();
1375 if (Character && (Character->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) Character->DisplayInfo(Msg);
1376 if (!(RAND()%10000) && (Square->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) Msg << " You see here a frog eating a magnolia.";
1377 ADD_MESSAGE("%s", Msg.CStr());
1378 if (GetSeeWholeMapCheatMode()) Square->SetMemorizedDescription(OldMemory);
1382 truth game::AnimationController () {
1383 DrawEverythingNoBlit(true);
1384 return true;
1388 void game::LoadGlobalValueMap (inputfile &fl) {
1389 festring word;
1390 fl.setGetVarCB(game::ldrGetVar);
1391 for (fl.ReadWord(word, false); !fl.Eof(); fl.ReadWord(word, false)) {
1392 if (word == "Include") {
1393 word = fl.ReadWord();
1394 if (fl.ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1395 //fprintf(stderr, "loading: %s\n", word.CStr());
1396 inputfile incf(game::GetGameDir()+"script/"+word, &game::GetGlobalValueMap());
1397 LoadGlobalValueMap(incf);
1398 continue;
1400 if (word == "Message") {
1401 word = fl.ReadWord();
1402 if (fl.ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1403 fprintf(stderr, "MESSAGE: %s\n", word.CStr());
1404 continue;
1406 if (word != "#") ABORT("Illegal datafile define in file %s on line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1407 fl.ReadWord(word, true);
1408 if (word == "enum" || word == "bitenum") {
1409 truth isBit = word == "bitenum";
1410 sLong idx = 0;
1411 if (fl.ReadWord() != "{") ABORT("'{' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1412 festring idName;
1413 truth done = false;
1414 while (!done) {
1415 fl.ReadWord(word, true);
1416 if (word == "}") break;
1417 if (word == "=") {
1418 idName.Empty();
1419 } else {
1420 idName = word;
1421 fl.ReadWord(word, true);
1423 if (word == "=") {
1424 // set current index
1425 idx = fl.ReadNumber();
1426 } else {
1427 if (word != "," && word != ";" && word != "}") ABORT("',' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1428 if (word == "}") done = true;
1430 if (idName.GetSize() > 0) {
1431 sLong i = idx;
1432 if (isBit) i = 1<<i;
1433 GlobalValueMap.insert(std::make_pair(idName, i));
1434 idx++;
1437 fl.SkipSpaces();
1438 int ch = fl.Get();
1439 if (ch != EOF && ch != ';') fl.Unget(ch);
1440 //if (fl.ReadWord() != ";") ABORT("';' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1441 continue;
1443 if (word == "define") {
1444 fl.ReadWord(word);
1445 sLong v = fl.ReadNumber();
1446 GlobalValueMap.insert(std::make_pair(word, v));
1447 continue;
1449 ABORT("Illegal datafile define in file %s on line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1454 void game::InitGlobalValueMap () {
1455 inputfile SaveFile(GetGameDir()+"script/define.dat", &GlobalValueMap);
1456 LoadGlobalValueMap(SaveFile);
1457 { /* additional files */
1458 for (int f = 0; f <= 99; f++) {
1459 char bnum[32];
1460 sprintf(bnum, "script/define_%02d.dat", f);
1461 festring fn = game::GetGameDir();
1462 fn << bnum;
1463 if (inputfile::fileExists(fn)) return;
1464 inputfile ifl(fn, &game::GetGlobalValueMap(), false);
1465 if (ifl.IsOpen()) {
1466 LoadGlobalValueMap(ifl);
1467 ifl.Close();
1474 void game::TextScreen (cfestring &Text, v2 Displacement, col16 Color, truth GKey, truth Fade, bitmapeditor BitmapEditor) {
1475 globalwindowhandler::DisableControlLoops();
1476 iosystem::TextScreen(Text, Displacement, Color, GKey, Fade, BitmapEditor);
1477 globalwindowhandler::EnableControlLoops();
1481 /* ... all the keys that are acceptable
1482 DefaultAnswer = REQUIRES_ANSWER if this question requires an answer
1483 Not surprisingly KeyNumber is the number of keys at ...
1485 int game::KeyQuestion (cfestring &Message, int DefaultAnswer, int KeyNumber, ...) {
1486 int *Key = new int[KeyNumber];
1487 va_list Arguments;
1488 va_start(Arguments, KeyNumber);
1489 for (int c = 0; c < KeyNumber; ++c) Key[c] = va_arg(Arguments, int);
1490 va_end(Arguments);
1491 DrawEverythingNoBlit();
1492 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Message.CStr());
1493 graphics::BlitDBToScreen();
1494 int Return = 0;
1495 while (!Return) {
1496 int k = GET_KEY();
1497 for (int c = 0; c < KeyNumber; ++c) {
1498 if (Key[c] == k) {
1499 Return = k;
1500 break;
1503 if (!Return && DefaultAnswer != REQUIRES_ANSWER) Return = DefaultAnswer;
1505 delete [] Key;
1506 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1507 return Return;
1511 v2 game::LookKeyHandler (v2 CursorPos, int Key) {
1512 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1513 switch (Key) {
1514 case 'i':
1515 if (!IsInWilderness()) {
1516 if (Square->CanBeSeenByPlayer() || CursorPos == Player->GetPos() || GetSeeWholeMapCheatMode()) {
1517 lsquare *LSquare = GetCurrentLevel()->GetLSquare(CursorPos);
1518 stack *Stack = LSquare->GetStack();
1519 if (LSquare->IsTransparent() && Stack->GetVisibleItems(Player))
1520 Stack->DrawContents(Player, "Items here", NO_SELECT|(GetSeeWholeMapCheatMode() ? 0 : NO_SPECIAL_INFO));
1521 else
1522 ADD_MESSAGE("You see no items here.");
1523 } else ADD_MESSAGE("You should perhaps move a bit closer.");
1525 break;
1526 case 'c':
1527 if (Square->CanBeSeenByPlayer() || CursorPos == Player->GetPos() || GetSeeWholeMapCheatMode()) {
1528 character *Char = Square->GetCharacter();
1529 if (Char && (Char->CanBeSeenByPlayer() || Char->IsPlayer() || GetSeeWholeMapCheatMode()))
1530 Char->PrintInfo();
1531 else
1532 ADD_MESSAGE("You see no one here.");
1533 } else ADD_MESSAGE("You should perhaps move a bit closer.");
1534 break;
1536 return CursorPos;
1540 v2 game::NameKeyHandler (v2 CursorPos, int Key) {
1541 if (SelectPet(Key)) return LastPetUnderCursor->GetPos();
1542 if (Key == 'n' || Key == 'N') {
1543 character *Char = GetCurrentArea()->GetSquare(CursorPos)->GetCharacter();
1544 if (Char && Char->CanBeSeenByPlayer()) Char->TryToName();
1545 else ADD_MESSAGE("You don't see anyone here to name.");
1547 return CursorPos;
1551 void game::End (festring DeathMessage, truth Permanently, truth AndGoToMenu) {
1552 pool::AbortBe();
1553 globalwindowhandler::DeInstallControlLoop(AnimationController);
1554 SetIsRunning(false);
1555 if (Permanently || !WizardModeIsReallyActive()) RemoveSaves(Permanently);
1556 if (Permanently && !WizardModeIsReallyActive()) {
1557 highscore HScore;
1559 if (HScore.LastAddFailed()) {
1560 iosystem::TextScreen(CONST_S("You didn't manage to get onto the high score list.\n\n\n\n")+GetPlayerName()+", "+DeathMessage+"\nRIP");
1561 } else {
1562 HScore.Draw();
1565 if (AndGoToMenu) {
1566 /* This prevents monster movement etc. after death. */
1567 throw quitrequest();
1572 int game::CalculateRoughDirection (v2 Vector) {
1573 if (!Vector.X && !Vector.Y) return YOURSELF;
1574 double Angle = femath::CalculateAngle(Vector);
1575 if (Angle < FPI / 8) return 4;
1576 else if (Angle < 3*FPI/8) return 7;
1577 else if (Angle < 5*FPI/8) return 6;
1578 else if (Angle < 7*FPI/8) return 5;
1579 else if (Angle < 9*FPI/8) return 3;
1580 else if (Angle < 11*FPI/8) return 0;
1581 else if (Angle < 13*FPI/8) return 1;
1582 else if (Angle < 15*FPI/8) return 2;
1583 else return 4;
1587 int game::Menu (bitmap *BackGround, v2 Pos, cfestring &Topic, cfestring &sMS, col16 Color, cfestring &SmallText1, cfestring &SmallText2) {
1588 globalwindowhandler::DisableControlLoops();
1589 int Return = iosystem::Menu(BackGround, Pos, Topic, sMS, Color, SmallText1, SmallText2);
1590 globalwindowhandler::EnableControlLoops();
1591 return Return;
1595 void game::InitDangerMap () {
1596 truth First = true;
1597 pool::RegisterState(false);
1598 //fprintf(stderr, "game::InitDangerMap(): START\n");
1599 for (int c1 = 1; c1 < protocontainer<character>::GetSize(); ++c1) {
1600 BusyAnimation();
1601 const character::prototype *Proto = protocontainer<character>::GetProto(c1);
1602 const character::database *const *ConfigData = Proto->GetConfigData();
1603 int ConfigSize = Proto->GetConfigSize();
1604 for (int c2 = 0; c2 < ConfigSize; ++c2) {
1605 if (!ConfigData[c2]->IsAbstract) {
1606 int Config = ConfigData[c2]->Config;
1607 if (First) {
1608 NextDangerIDType = c1;
1609 NextDangerIDConfigIndex = c2;
1610 First = false;
1612 character *Char = Proto->Spawn(Config, NO_EQUIPMENT|NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1613 double NakedDanger = Char->GetRelativeDanger(Player, true);
1614 //fprintf(stderr, " game::InitDangerMap(): 00\n");
1615 delete Char;
1616 //Char->SendToHell(); // k8: equipment
1617 Char = Proto->Spawn(Config, NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1618 double EquippedDanger = Char->GetRelativeDanger(Player, true);
1619 //fprintf(stderr, " game::InitDangerMap(): 01\n");
1620 delete Char;
1621 //Char->SendToHell(); // k8: equipment
1622 DangerMap[configid(c1, Config)] = dangerid(NakedDanger, EquippedDanger);
1626 pool::RegisterState(true);
1627 //fprintf(stderr, "game::InitDangerMap(): DONE\n");
1631 void game::CalculateNextDanger () {
1632 if (IsInWilderness() || !*CurrentLevel->GetLevelScript()->GenerateMonsters()) return;
1633 const character::prototype *Proto = protocontainer<character>::GetProto(NextDangerIDType);
1634 const character::database *const *ConfigData = Proto->GetConfigData();
1635 const character::database *DataBase = ConfigData[NextDangerIDConfigIndex];
1636 dangermap::iterator DangerIterator = DangerMap.find(configid(NextDangerIDType, DataBase->Config));
1637 team *Team = GetTeam(PLAYER_TEAM);
1638 if (DataBase && DangerIterator != DangerMap.end()) {
1639 //fprintf(stderr, "game::CalculateNextDanger(): START\n");
1640 pool::RegisterState(false);
1641 character *Char = Proto->Spawn(DataBase->Config, NO_EQUIPMENT|NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1642 std::list<character*>::const_iterator i;
1643 double DangerSum = Player->GetRelativeDanger(Char, true);
1644 for (i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i) {
1645 if ((*i)->IsEnabled() && !(*i)->IsTemporary() && !RAND_N(10)) DangerSum += (*i)->GetRelativeDanger(Char, true)/4;
1647 double CurrentDanger = 1/DangerSum;
1648 double NakedDanger = DangerIterator->second.NakedDanger;
1649 //fprintf(stderr, " game::CalculateNextDanger(): 00\n");
1650 delete Char;
1651 //Char->SendToHell(); // k8: equipment
1652 if (NakedDanger > CurrentDanger) DangerIterator->second.NakedDanger = (NakedDanger*9+CurrentDanger)/10;
1653 Char = Proto->Spawn(DataBase->Config, NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1654 DangerSum = Player->GetRelativeDanger(Char, true);
1655 for (i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i) {
1656 if ((*i)->IsEnabled() && !(*i)->IsTemporary() && !RAND_N(10)) DangerSum += (*i)->GetRelativeDanger(Char, true) / 4;
1658 CurrentDanger = 1/DangerSum;
1659 double EquippedDanger = DangerIterator->second.EquippedDanger;
1660 //fprintf(stderr, " game::CalculateNextDanger(): 01\n");
1661 delete Char;
1662 //Char->SendToHell(); // k8: equipment
1663 pool::RegisterState(true);
1664 if (EquippedDanger > CurrentDanger) DangerIterator->second.EquippedDanger = (EquippedDanger*9+CurrentDanger)/10;
1665 if (++NextDangerIDConfigIndex < Proto->GetConfigSize()) {
1666 //fprintf(stderr, "game::CalculateNextDanger(): EXIT0\n");
1667 return;
1669 for (;;) {
1670 if (++NextDangerIDType >= protocontainer<character>::GetSize()) NextDangerIDType = 1;
1671 Proto = protocontainer<character>::GetProto(NextDangerIDType);
1672 ConfigData = Proto->GetConfigData();
1673 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::GetSavePath () {
2025 festring Dir;
2026 #ifdef LOCAL_SAVES
2027 Dir << ivanconfig::GetMyDir() << "/save/";
2028 #else
2029 Dir << getenv("HOME") << "/.ivan-save/";
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::GetBonePath () {
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") << "/.ivan-save/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 = GetBonePath()+"bon"+CurrentDungeonIndex+CurrentLevelIndex+BoneIndex;
2224 if (!inputfile::fileExists(BoneName)) break;
2226 if (BoneIndex != 1000) {
2227 //festring BoneName = GetBonePath()+"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 = GetBonePath()+"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 if (Temp == "socm") Temp = "scroll of change material";
2646 else if (Temp == "soc") Temp = "scroll of charging";
2647 else if (Temp == "sodm") Temp = "scroll of detect material";
2648 else if (Temp == "soea") Temp = "scroll of enchant armor";
2649 else if (Temp == "soew") Temp = "scroll of enchant weapon";
2650 else if (Temp == "sof") Temp = "scroll of fireballs";
2651 else if (Temp == "sogc") Temp = "scroll of golem creation";
2652 else if (Temp == "sohm") Temp = "scroll of harden material";
2653 else if (Temp == "sor") Temp = "scroll of repair";
2654 else if (Temp == "sot") Temp = "scroll of taming";
2655 else if (Temp == "sotp") Temp = "scroll of teleportation";
2656 else if (Temp == "sow") Temp = "scroll of wishing";
2657 else if (Temp == "vodka") Temp = "bottle full of vodka";
2658 else if (Temp == "troll blood") Temp = "bottle full of troll blood";
2659 item *TempItem = protosystem::CreateItem(Temp, Wisher->IsPlayer());
2660 if (TempItem) {
2661 Wisher->GetStack()->AddItem(TempItem);
2662 TempItem->SpecialGenerationHandler();
2663 if (TempItem->HandleInPairs()) ADD_MESSAGE(MsgPair, TempItem->CHAR_NAME(PLURAL));
2664 else ADD_MESSAGE(MsgSingle, TempItem->CHAR_NAME(INDEFINITE));
2665 return true;
2671 festring game::DefaultQuestion (festring Topic, festring &Default, stringkeyhandler KeyHandler) {
2672 festring ShortDefault = Default;
2673 if (Default.GetSize() > 29) {
2674 ShortDefault.Resize(27);
2675 ShortDefault = ShortDefault << CONST_S("...");
2677 if (!Default.IsEmpty()) Topic << " [" << ShortDefault << ']';
2678 festring Answer = StringQuestion(Topic, WHITE, 0, 80, false, KeyHandler);
2679 if (Answer.IsEmpty()) Answer = Default;
2680 return Default = Answer;
2684 void game::GetTime (ivantime &Time) {
2685 Time.Hour = 12 + Tick / 2000;
2686 Time.Day = Time.Hour / 24 + 1;
2687 Time.Hour %= 24;
2688 Time.Min = Tick % 2000 * 60 / 2000;
2692 truth NameOrderer (character *C1, character *C2) {
2693 return festring::IgnoreCaseCompare(C1->GetName(UNARTICLED), C2->GetName(UNARTICLED));
2697 truth game::PolymorphControlKeyHandler (int Key, festring &String) {
2698 if (Key == '?') {
2699 felist List(CONST_S("List of known creatures and their intelligence requirements"));
2700 SetStandardListAttributes(List);
2701 List.SetPageLength(15);
2702 List.AddFlags(SELECTABLE);
2703 protosystem::CreateEverySeenCharacter(CharacterDrawVector);
2704 std::sort(CharacterDrawVector.begin(), CharacterDrawVector.end(), NameOrderer);
2705 List.SetEntryDrawer(CharacterEntryDrawer);
2706 std::vector<festring> StringVector;
2707 uInt c;
2708 for (c = 0; c < CharacterDrawVector.size(); ++c) {
2709 character *Char = CharacterDrawVector[c];
2710 if (Char->CanBeWished()) {
2711 festring Entry;
2712 Char->AddName(Entry, UNARTICLED);
2713 StringVector.push_back(Entry);
2714 int Req = Char->GetPolymorphIntelligenceRequirement();
2715 if (Char->IsSameAs(Player) || (Player->GetPolymorphBackup() && Player->GetPolymorphBackup()->IsSameAs(Char))) Req = 0;
2716 Entry << " (" << Req << ')';
2717 int Int = Player->GetAttribute(INTELLIGENCE);
2718 List.AddEntry(Entry, Req > Int ? RED : LIGHT_GRAY, 0, c);
2721 int Chosen = List.Draw();
2722 for (c = 0; c < CharacterDrawVector.size(); ++c) delete CharacterDrawVector[c];
2723 if (!(Chosen & FELIST_ERROR_BIT)) String = StringVector[Chosen];
2724 CharacterDrawVector.clear();
2725 return true;
2727 return false;
2731 outputfile &operator << (outputfile &SaveFile, const killdata &Value) {
2732 SaveFile << Value.Amount << Value.DangerSum << Value.Reason;
2733 return SaveFile;
2737 inputfile &operator >> (inputfile &SaveFile, killdata &Value) {
2738 SaveFile >> Value.Amount >> Value.DangerSum >> Value.Reason;
2739 return SaveFile;
2743 outputfile &operator << (outputfile &SaveFile, const killreason &Value) {
2744 SaveFile << Value.Amount << Value.String;
2745 return SaveFile;
2749 inputfile &operator >> (inputfile &SaveFile, killreason &Value) {
2750 SaveFile >> Value.Amount >> Value.String;
2751 return SaveFile;
2755 truth DistanceOrderer (character *C1, character *C2) {
2756 v2 PlayerPos = PLAYER->GetPos();
2757 v2 Pos1 = C1->GetPos();
2758 v2 Pos2 = C2->GetPos();
2759 int D1 = Max(abs(Pos1.X - PlayerPos.X), abs(Pos1.Y - PlayerPos.Y));
2760 int D2 = Max(abs(Pos2.X - PlayerPos.X), abs(Pos2.Y - PlayerPos.Y));
2761 if (D1 != D2) return D1 < D2;
2762 if (Pos1.Y != Pos2.Y) return Pos1.Y < Pos2.Y;
2763 return Pos1.X < Pos2.X;
2767 truth game::FillPetVector (cchar *Verb) {
2768 PetVector.clear();
2769 team *Team = GetTeam(PLAYER_TEAM);
2770 for (std::list<character*>::const_iterator i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i)
2771 if ((*i)->IsEnabled() && !(*i)->IsPlayer() && (*i)->CanBeSeenByPlayer()) PetVector.push_back(*i);
2772 if (PetVector.empty()) {
2773 ADD_MESSAGE("You don't detect any friends to %s.", Verb);
2774 return false;
2776 std::sort(PetVector.begin(), PetVector.end(), DistanceOrderer);
2777 LastPetUnderCursor = PetVector[0];
2778 return true;
2782 truth game::CommandQuestion () {
2783 if (!FillPetVector("command")) return false;
2784 character *Char;
2785 if (PetVector.size() == 1) Char = PetVector[0];
2786 else {
2787 v2 Pos = PetVector[0]->GetPos();
2788 Pos = PositionQuestion(CONST_S("Whom do you wish to command? [direction keys/'+'/'-'/'a'll/space/esc]"), Pos, &PetHandler, &CommandKeyHandler);
2789 if (Pos == ERROR_V2) return false;
2790 if (Pos == ABORT_V2) return true;
2791 Char = CurrentArea->GetSquare(Pos)->GetCharacter();
2792 if (!Char || !Char->CanBeSeenByPlayer()) {
2793 ADD_MESSAGE("You don't see anyone here to command.");
2794 return false;
2796 if (Char->IsPlayer()) {
2797 ADD_MESSAGE("You do that all the time.");
2798 return false;
2800 if (!Char->IsPet()) {
2801 ADD_MESSAGE("%s refuses to be commanded by you.", Char->CHAR_NAME(DEFINITE));
2802 return false;
2805 return Char->IssuePetCommands();
2809 void game::NameQuestion () {
2810 if (!FillPetVector("name")) return;
2811 if (PetVector.size() == 1) PetVector[0]->TryToName();
2812 else PositionQuestion(CONST_S("Who do you want to name? [direction keys/'+'/'-'/'n'ame/esc]"), PetVector[0]->GetPos(), &PetHandler, &NameKeyHandler);
2816 void game::PetHandler (v2 CursorPos) {
2817 character *Char = CurrentArea->GetSquare(CursorPos)->GetCharacter();
2818 if (Char && Char->CanBeSeenByPlayer() && Char->IsPet() && !Char->IsPlayer()) CursorData = RED_CURSOR|CURSOR_TARGET;
2819 else CursorData = RED_CURSOR;
2820 if (Char && !Char->IsPlayer() && Char->IsPet()) LastPetUnderCursor = Char;
2824 v2 game::CommandKeyHandler (v2 CursorPos, int Key) {
2825 if (SelectPet(Key)) return LastPetUnderCursor->GetPos();
2826 if (Key == 'a' || Key == 'A') return CommandAll() ? ABORT_V2 : ERROR_V2;
2827 return CursorPos;
2831 truth game::SelectPet (int Key) {
2832 if (Key == '+') {
2833 for (uInt c = 0; c < PetVector.size(); ++c) {
2834 if (PetVector[c] == LastPetUnderCursor) {
2835 if (++c == PetVector.size()) c = 0;
2836 LastPetUnderCursor = PetVector[c];
2837 return true;
2840 } else if (Key == '-') {
2841 for (uInt c = 0; c < PetVector.size(); ++c) {
2842 if (PetVector[c] == LastPetUnderCursor) {
2843 if (!c) c = PetVector.size();
2844 LastPetUnderCursor = PetVector[--c];
2845 return true;
2849 return false;
2853 void game::CommandScreen (cfestring &Topic, feuLong PossibleFlags, feuLong ConstantFlags, feuLong &VaryFlags, feuLong &Flags) {
2854 static cchar *CommandDescription[COMMAND_FLAGS] = {
2855 "Follow me",
2856 "Flee from enemies",
2857 "Don't change your equipment",
2858 "Don't consume anything valuable"
2860 felist List(Topic);
2861 SetStandardListAttributes(List);
2862 List.AddFlags(SELECTABLE);
2863 List.AddDescription(CONST_S(""));
2864 List.AddDescription(CONST_S("Command Active?"));
2865 for (;;) {
2866 int c, i;
2867 for (c = 0; c < COMMAND_FLAGS; ++c) {
2868 if (1 << c & PossibleFlags) {
2869 truth Changeable = !(1 << c & ConstantFlags);
2870 festring Entry;
2871 if (Changeable) {
2872 Entry = CommandDescription[c];
2873 Entry.Resize(60);
2874 } else {
2875 Entry << " " << CommandDescription[c];
2876 Entry.Resize(63);
2878 if (1 << c & VaryFlags) Entry << "varies"; else Entry << (1 << c & Flags ? "yes" : "no");
2879 List.AddEntry(Entry, Changeable ? LIGHT_GRAY : DARK_GRAY, 0, NO_IMAGE, Changeable);
2882 int Chosen = List.Draw();
2883 if (Chosen & FELIST_ERROR_BIT) return;
2884 for (c = 0, i = 0; c < COMMAND_FLAGS; ++c) {
2885 if (1 << c & PossibleFlags && !(1 << c & ConstantFlags) && i++ == Chosen) {
2886 if (1 << c & VaryFlags) {
2887 VaryFlags &= ~(1 << c);
2888 Flags |= 1 << c;
2889 } else Flags ^= 1 << c;
2890 break;
2893 List.Empty();
2894 DrawEverythingNoBlit();
2899 truth game::CommandAll () {
2900 feuLong PossibleFlags = 0, ConstantFlags = ALL_COMMAND_FLAGS, VaryFlags = 0, OldFlags = 0;
2901 uInt c1, c2;
2902 for (c1 = 0; c1 < PetVector.size(); ++c1) {
2903 ConstantFlags &= PetVector[c1]->GetConstantCommandFlags();
2904 feuLong C = PetVector[c1]->GetCommandFlags();
2905 feuLong ThisPossible = PetVector[c1]->GetPossibleCommandFlags();
2906 for (c2 = 0; c2 < COMMAND_FLAGS; ++c2)
2907 if (1 << c2 & PossibleFlags & ThisPossible && (1 << c2 & C) != (1 << c2 & OldFlags)) VaryFlags |= 1 << c2;
2908 PossibleFlags |= ThisPossible;
2909 OldFlags |= C & ThisPossible;
2911 if (!PossibleFlags) {
2912 ADD_MESSAGE("Not a single creature in your visible team can be commanded.");
2913 return false;
2915 feuLong NewFlags = OldFlags;
2916 CommandScreen(CONST_S("Issue commands to whole visible team"), PossibleFlags, ConstantFlags, VaryFlags, NewFlags);
2917 truth Change = false;
2918 for (c1 = 0; c1 < PetVector.size(); ++c1) {
2919 character *Char = PetVector[c1];
2920 if (!Char->IsConscious()) continue;
2921 feuLong OldC = Char->GetCommandFlags();
2922 feuLong ConstC = Char->GetConstantCommandFlags();
2923 feuLong ThisC = (NewFlags & Char->GetPossibleCommandFlags() & ~(ConstC|VaryFlags)) | (OldC & (ConstC|VaryFlags));
2924 if (ThisC != OldC) Change = true;
2925 Char->SetCommandFlags(ThisC);
2927 if (!Change) return false;
2928 Player->EditAP(-500);
2929 Player->EditExperience(CHARISMA, 50, 1 << 7);
2930 return true;
2934 col16 game::GetAttributeColor (int I) {
2935 int Delta = GetTick()-LastAttributeChangeTick[I];
2936 if (OldAttribute[I] == NewAttribute[I] || Delta >= 510) return WHITE;
2937 if (OldAttribute[I] < NewAttribute[I]) return MakeRGB16(255, 255, Delta >> 1);
2938 return MakeRGB16(255, Delta >> 1, Delta >> 1);
2942 void game::UpdateAttributeMemory () {
2943 for (int c = 0; c < ATTRIBUTES; ++c) {
2944 int A = Player->GetAttribute(c);
2945 if (A != NewAttribute[c]) {
2946 OldAttribute[c] = NewAttribute[c];
2947 NewAttribute[c] = A;
2948 LastAttributeChangeTick[c] = GetTick();
2954 void game::InitAttributeMemory () {
2955 for (int c = 0; c < ATTRIBUTES; ++c) OldAttribute[c] = NewAttribute[c] = Player->GetAttribute(c);
2959 void game::TeleportHandler (v2 CursorPos) {
2960 if ((CursorPos-Player->GetPos()).GetLengthSquare() > Player->GetTeleportRangeSquare())
2961 CursorData = BLUE_CURSOR|CURSOR_TARGET;
2962 else
2963 CursorData = RED_CURSOR|CURSOR_TARGET;
2967 double game::GetGameSituationDanger () {
2968 double SituationDanger = 0;
2969 character *Player = GetPlayer();
2970 truth PlayerStuck = Player->IsStuck();
2971 v2 PlayerPos = Player->GetPos();
2972 character *TruePlayer = Player;
2973 if (PlayerStuck) (Player = Player->Duplicate(IGNORE_PROHIBITIONS))->ChangeTeam(0);
2974 for (int c1 = 0; c1 < GetTeams(); ++c1)
2975 if (GetTeam(c1)->GetRelation(GetTeam(PLAYER_TEAM)) == HOSTILE)
2976 for (std::list<character*>::const_iterator i1 = GetTeam(c1)->GetMember().begin(); i1 != GetTeam(c1)->GetMember().end(); ++i1) {
2977 character *Enemy = *i1;
2978 if (Enemy->IsEnabled() && Enemy->CanAttack() && (Enemy->CanMove() || Enemy->GetPos().IsAdjacent(PlayerPos))) {
2979 truth EnemyStuck = Enemy->IsStuck();
2980 v2 EnemyPos = Enemy->GetPos();
2981 truth Sees = TruePlayer->CanBeSeenBy(Enemy);
2982 character *TrueEnemy = Enemy;
2983 if (EnemyStuck) Enemy = Enemy->Duplicate(IGNORE_PROHIBITIONS);
2984 double PlayerTeamDanger = 1/Enemy->GetSituationDanger(Player, EnemyPos, PlayerPos, Sees);
2985 for (int c2 = 0; c2 < GetTeams(); ++c2)
2986 if (GetTeam(c2)->GetRelation(GetTeam(c1)) == HOSTILE)
2987 for (std::list<character*>::const_iterator i2 = GetTeam(c2)->GetMember().begin(); i2 != GetTeam(c2)->GetMember().end(); ++i2) {
2988 character *Friend = *i2;
2989 if (Friend->IsEnabled() && !Friend->IsPlayer() && Friend->CanAttack() && (Friend->CanMove() || Friend->GetPos().IsAdjacent(EnemyPos))) {
2990 v2 FriendPos = Friend->GetPos();
2991 truth Sees = TrueEnemy->CanBeSeenBy(Friend);
2992 if (Friend->IsStuck()) {
2993 Friend = Friend->Duplicate(IGNORE_PROHIBITIONS);
2994 PlayerTeamDanger += Friend->GetSituationDanger(Enemy, FriendPos, EnemyPos, Sees) * .2;
2995 delete Friend;
2996 } else PlayerTeamDanger += Friend->GetSituationDanger(Enemy, FriendPos, EnemyPos, Sees);
2999 if (EnemyStuck) {
3000 PlayerTeamDanger *= 5;
3001 delete Enemy;
3003 SituationDanger += 1 / PlayerTeamDanger;
3006 Player->ModifySituationDanger(SituationDanger);
3007 if (PlayerStuck) {
3008 SituationDanger *= 2;
3009 delete Player;
3011 return SituationDanger;
3015 sLong game::GetTimeSpent () {
3016 return time::TimeAdd(time::TimeDifference(time(0),LastLoad), TimePlayedBeforeLastLoad);
3020 outputfile &operator << (outputfile &SaveFile, const massacreid &MI) {
3021 SaveFile << MI.Type << MI.Config << MI.Name;
3022 return SaveFile;
3026 inputfile &operator >> (inputfile &SaveFile, massacreid &MI) {
3027 SaveFile >> MI.Type >> MI.Config >> MI.Name;
3028 return SaveFile;
3032 truth game::PlayerIsRunning () {
3033 return PlayerRunning && Player->CanMove();
3037 void game::AddSpecialCursor (v2 Pos, int Data) {
3038 SpecialCursorPos.push_back(Pos);
3039 SpecialCursorData.push_back(Data);
3043 void game::RemoveSpecialCursors () {
3044 SpecialCursorPos.clear();
3045 SpecialCursorData.clear();
3049 void game::LearnAbout (god *Who) {
3050 Who->SetIsKnown(true);
3051 /* slightly slow, but doesn't matter since this is run so rarely */
3052 if (PlayerKnowsAllGods() && !game::PlayerHasReceivedAllGodsKnownBonus) {
3053 GetPlayer()->ApplyAllGodsKnownBonus();
3054 game::PlayerHasReceivedAllGodsKnownBonus = true;
3059 truth game::PlayerKnowsAllGods () {
3060 for (int c = 1; c <= GODS; ++c) if (!GetGod(c)->IsKnown()) return false;
3061 return true;
3065 void game::AdjustRelationsToAllGods (int Amount) {
3066 for (int c = 1; c <= GODS; ++c) GetGod(c)->AdjustRelation(Amount);
3070 void game::SetRelationsToAllGods (int Amount) {
3071 for (int c = 1; c <= GODS; ++c) GetGod(c)->SetRelation(Amount);
3075 void game::ShowDeathSmiley (bitmap *Buffer, truth) {
3076 static blitdata B = {
3078 { 0, 0 },
3079 { (RES.X >> 1) - 24, RES.Y * 4 / 7 - 24 },
3080 { 48, 48 },
3081 { 0 },
3082 TRANSPARENT_COLOR,
3085 int Tick = globalwindowhandler::UpdateTick();
3086 if (((Tick >> 1) & 31) == 1) B.Src.X = 48;
3087 else if (((Tick >> 1) & 31) == 2) B.Src.X = 96;
3088 else B.Src.X = 0;
3089 B.Bitmap = Buffer;
3090 igraph::GetSmileyGraphic()->NormalBlit(B);
3091 if (Buffer == DOUBLE_BUFFER) graphics::BlitDBToScreen();
3095 static int doListSelector (felist &list, int defsel, int cnt) {
3096 game::SetStandardListAttributes(list);
3097 list.AddFlags(SELECTABLE | FELIST_NO_BADKEY_EXIT);
3098 if (defsel > 0) list.SetSelected(defsel);
3099 uInt sel = list.Draw();
3100 list.Empty();
3101 list.RemoveFlags(SELECTABLE | FELIST_NO_BADKEY_EXIT);
3102 if (sel & FELIST_ERROR_BIT) return -1;
3103 if (sel >= (uInt)cnt) return -1;
3104 return (int)sel;
3108 int game::ListSelector (int defsel, const cfestring title, ...) {
3109 int cnt = 0;
3110 va_list items;
3111 va_start(items, title);
3113 felist list(title);
3114 for (;;) {
3115 const char *s = va_arg(items, const char *);
3116 if (!s) break;
3117 list.AddEntry(s, LIGHT_GRAY);
3118 cnt++;
3120 va_end(items);
3121 return doListSelector(list, defsel, cnt);
3125 int game::ListSelectorArray (int defsel, cfestring &title, const char *items[]) {
3126 int cnt = 0;
3127 felist list(title);
3128 for (;;) {
3129 if (!items[cnt]) break;
3130 list.AddEntry(items[cnt], LIGHT_GRAY);
3131 cnt++;
3133 return doListSelector(list, defsel, cnt);
3137 void game::ClearEventData () {
3138 mChar = 0;
3139 mActor = 0;
3140 mSecondActor = 0;
3141 mItem = 0;
3145 // '.': string or number
3146 // 'n': number
3147 // 's': string
3148 // '*': collect all args
3149 int game::ParseFuncArgs (cfestring &types, std::vector<FuncArg> &args, inputfile *fl, truth noterm) {
3150 festring s;
3151 sLong n;
3152 truth isStr;
3153 if (!fl) fl = mFEStack.top();
3154 args.clear();
3155 for (unsigned int f = 0; f < types.GetSize(); f++) {
3156 switch (types[f]) {
3157 case '.':
3158 s = fl->ReadStringOrNumber(&n, &isStr, true);
3159 if (isStr) args.push_back(FuncArg(s)); else args.push_back(FuncArg(n));
3160 break;
3161 case 'n':
3162 n = fl->ReadNumber(0xFF, true);
3163 args.push_back(FuncArg(n));
3164 break;
3165 case '*':
3166 for (;;) {
3167 s = fl->ReadStringOrNumber(&n, &isStr, true);
3168 if (isStr) args.push_back(FuncArg(s)); else args.push_back(FuncArg(n));
3169 fl->ReadWord(s, true);
3170 if (s == ";") return args.size();
3171 if (s != ",") ABORT("',' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3173 // never reached
3174 case 's':
3175 default:
3176 s = fl->ReadWord(true);
3177 args.push_back(FuncArg(s));
3178 break;
3180 if (f == types.GetSize()-1) {
3181 if (noterm) break;
3182 fl->ReadWord(s, true);
3183 if (s != ";") ABORT("';' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3184 break;
3185 } else {
3186 fl->ReadWord(s, true);
3187 if (s != ",") ABORT("',' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3190 return args.size();
3194 truth game::GetWord (festring &w) {
3195 for (;;) {
3196 inputfile *fl = mFEStack.top();
3197 fl->ReadWord(w, false);
3198 if (w == "" && fl->Eof()) {
3199 delete fl;
3200 mFEStack.pop();
3201 if (mFEStack.empty()) return false;
3202 continue;
3204 if (w == "Include") {
3205 fl->ReadWord(w, true);
3206 if (fl->ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3207 w = game::GetGameDir()+"script/"+w;
3208 inputfile *fl = new inputfile(w, &game::GetGlobalValueMap(), true);
3209 fl->setGetVarCB(game::ldrGetVar);
3210 mFEStack.push(fl);
3211 continue;
3213 if (w == "Message") {
3214 fl->ReadWord(w, true);
3215 if (fl->ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl->GetFileName().CStr(), fl->TokenLine());
3216 fprintf(stderr, "MESSAGE: %s\n", w.CStr());
3217 continue;
3219 return true;
3224 void game::SkipBlock (truth brcEaten) {
3225 festring w;
3226 if (!brcEaten) {
3227 mFEStack.top()->ReadWord(w, true);
3228 if (w != "{") ABORT("'{' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3230 int cnt = 1;
3231 for (;;) {
3232 mFEStack.top()->ReadWord(w, true);
3233 if (w == "{") cnt++;
3234 else if (w == "}") {
3235 if (--cnt < 1) break;
3241 truth game::DoOnEvent (truth brcEaten, truth AllowScript) {
3242 // do; only funcalls for now
3243 truth eaten = AllowScript ? true : false;
3244 festring w;
3245 if (!brcEaten) {
3246 mFEStack.top()->ReadWord(w, true);
3247 if (w != "{") ABORT("'{' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3249 for (;;) {
3250 if (!GetWord(w)) {
3251 if (AllowScript) break;
3252 ABORT("Unexpected end of file %s!", mFEStack.top()->GetFileName().CStr());
3254 //fprintf(stderr, " :[%s]\n", w.CStr());
3255 if (w == "}") {
3256 if (AllowScript) ABORT("Unexpected '}' in AllowScript file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3257 break;
3259 if (w == ";") continue;
3260 if (w == "@") {
3261 mFEStack.top()->ReadWord(w, true);
3262 if (mFEStack.top()->ReadWord(true) != "=") ABORT("'=' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3263 //fprintf(stderr, "setvar: %s\n", w.CStr());
3264 if (w == "money") {
3265 sLong n = mFEStack.top()->ReadNumber(true);
3266 if (n < 0) n = 0;
3267 if (mChar) mChar->SetMoney(n);
3268 continue;
3270 if (w == "result") {
3271 mResult = mFEStack.top()->ReadNumber(true);
3272 continue;
3274 ABORT("Unknown var [%s] in file %s at line %d!", w.CStr(), mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3275 } else {
3276 //mFEStack.top()->ReadWord(w, true);
3277 std::vector<FuncArg> args;
3278 //fprintf(stderr, "funcall: %s\n", w.CStr());
3279 if (w == "SetMoney") {
3280 ParseFuncArgs("n", args);
3281 sLong n = args[0].ival;
3282 if (n < 0) n = 0;
3283 if (mChar) mChar->SetMoney(n);
3284 continue;
3286 if (w == "EditMoney") {
3287 ParseFuncArgs("n", args);
3288 sLong n = args[0].ival;
3289 if (mChar) mChar->EditMoney(n);
3290 continue;
3292 if (w == "AddMessage") {
3293 ParseFuncArgs("*", args);
3294 festring s;
3295 for (uInt f = 0; f < args.size(); f++) {
3296 const FuncArg &a = args[f];
3297 if (a.type == FARG_STRING) s << a.sval; else s << a.ival;
3299 ADD_MESSAGE("%s", s.CStr());
3300 continue;
3302 if (w == "EatThisEvent") {
3303 if (AllowScript) ABORT("'EatThisEvent' forbidden in AllowScripts in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3304 eaten = true;
3305 continue;
3307 if (w == "Disallow") {
3308 if (!AllowScript) ABORT("'Disallow' forbidden in not-AllowScripts in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3309 eaten = false;
3310 continue;
3312 ABORT("Unknown function [%s] in file %s at line %d!", w.CStr(), mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3313 //if (mFEStack.top()->ReadWord() != ";") ABORT("';' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3315 //ABORT("Invalid term in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3317 //fprintf(stderr, "------------\n");
3318 return eaten;
3322 //TODO: cache event scripts
3323 truth game::RunOnEvent (cfestring &ename) {
3324 static std::vector<festring> scriptFiles;
3325 static truth cached = false;
3326 truth res = false;
3328 character *old = mChar;
3329 mChar = PLAYER;
3330 if (!cached) {
3331 cached = true;
3332 for (int fno = 99; fno >= -1; fno--) {
3333 festring cfname;
3334 cfname << game::GetGameDir() << "script/onevent";
3335 if (fno >= 0) {
3336 char bnum[8];
3337 sprintf(bnum, "_%02d", fno);
3338 cfname << bnum;
3340 cfname << ".dat";
3341 if (!inputfile::fileExists(cfname)) continue;
3342 inputfile *ifl = new inputfile(cfname, &game::GetGlobalValueMap(), false);
3343 if (!ifl->IsOpen()) {
3344 delete ifl;
3345 continue;
3347 scriptFiles.push_back(cfname);
3348 ifl->setGetVarCB(game::ldrGetVar);
3349 mFEStack.push(ifl);
3351 } else {
3352 for (unsigned int f = 0; f < scriptFiles.size(); ++f) {
3353 festring cfname = scriptFiles[f];
3354 inputfile *ifl = new inputfile(cfname, &game::GetGlobalValueMap(), false);
3355 if (!ifl->IsOpen()) {
3356 delete ifl;
3357 continue;
3359 ifl->setGetVarCB(game::ldrGetVar);
3360 mFEStack.push(ifl);
3364 festring w;
3365 while (GetWord(w)) {
3366 if (w != "on") ABORT("'on' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3367 mFEStack.top()->ReadWord(w, true);
3368 truth doIt = (w==ename);
3369 if (doIt && !res) {
3370 res = DoOnEvent(false);
3371 } else {
3372 // skip
3373 SkipBlock(false);
3376 mChar = old;
3377 return res;
3381 truth game::RunOnEventStr (cfestring &ename, cfestring &str) {
3382 truth res = false;
3383 if (str.GetSize() < 1) return false;
3384 //fprintf(stderr, "=============\n%s=============\n", str.CStr());
3385 inputfile *ifl = new meminputfile(str, &game::GetGlobalValueMap());
3386 ifl->setGetVarCB(game::ldrGetVar);
3387 mFEStack.push(ifl);
3388 festring w;
3389 //fprintf(stderr, "=============\n", str.CStr());
3390 //fprintf(stderr, "event: [%s]\n", ename.CStr());
3391 //fprintf(stderr, "---\n%s---\n", str.CStr());
3392 while (GetWord(w)) {
3393 if (w != "on") ABORT("'on' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TokenLine());
3394 mFEStack.top()->ReadWord(w, true);
3395 //fprintf(stderr, "on: [%s]\n", w.CStr());
3396 truth doIt = (w==ename);
3397 if (doIt && !res) {
3398 //fprintf(stderr, " do it\n");
3399 res = DoOnEvent(false);
3400 } else {
3401 // skip
3402 //fprintf(stderr, " skip it\n");
3403 SkipBlock(false);
3406 return res;
3410 truth game::RunOnCharEvent (character *who, cfestring &ename) {
3411 truth res = false;
3412 if (!who) return false;
3413 character *old = mChar;
3414 mChar = who;
3415 res = RunOnEventStr(ename, who->mOnEvents);
3416 if (!res) res = RunOnEventStr(ename, who->GetProtoType()->mOnEvents);
3417 mChar = old;
3418 return res;
3422 truth game::RunOnItemEvent (item *what, cfestring &ename) {
3423 truth res = false;
3424 if (!what) return false;
3425 item *old = mItem;
3426 mItem = what;
3427 res = RunOnEventStr(ename, what->mOnEvents);
3428 if (!res) res = RunOnEventStr(ename, what->GetProtoType()->mOnEvents);
3429 mItem = old;
3430 return res;
3434 festring game::ldrGetVar (inputfile *fl, cfestring &name) {
3435 //fprintf(stderr, "GETVAR: [%s]\n", name.CStr());
3436 if (name == "player_name") {
3437 return game::GetPlayerName();
3439 if (name == "money") {
3440 festring res;
3441 if (!mChar) return "0";
3442 res << mChar->GetMoney();
3443 return res;
3445 if (name == "name") {
3446 if (!mChar) return "";
3447 return mChar->GetAssignedName();
3449 if (name == "team") {
3450 festring res;
3451 if (!mChar) return "";
3452 res << mChar->GetTeam()->GetID();
3453 return res;
3455 if (name == "friendly") {
3456 festring res;
3457 if (!mChar || !PLAYER || mChar->GetRelation(PLAYER) != HOSTILE) return "tan";
3458 return "";
3460 if (name == "hostile") {
3461 festring res;
3462 if (!mChar || !PLAYER) return "";
3463 if (mChar->GetRelation(PLAYER) == HOSTILE) return "tan";
3464 return "";
3466 if (name == "has_item") {
3467 std::vector<FuncArg> args;
3468 ParseFuncArgs("s", args, fl, true);
3470 if (PLAYER) {
3471 itemvector items;
3472 festring s = args[0].sval;
3474 //fprintf(stderr, "looking for [%s]\n", s.CStr());
3475 PLAYER->GetStack()->FillItemVector(items);
3476 for (unsigned int f = 0; f < items.size(); ++f) {
3477 for (uInt c = 0; c < items[f]->GetDataBase()->Alias.Size; ++c) {
3478 //fprintf(stderr, "%u:%u: [%s]\n", f, c, items[f]->GetDataBase()->Alias[c].CStr());
3479 if (s.CompareIgnoreCase(items[f]->GetDataBase()->Alias[c]) == 0) {
3480 //fprintf(stderr, " FOUND!\n");
3481 return "tan";
3485 //fprintf(stderr, "checking equipment...\n");
3486 for (int f = 0; f < PLAYER->GetEquipments(); ++f) {
3487 item *it = PLAYER->GetEquipment(f);
3489 if (it) {
3490 for (uInt c = 0; c < it->GetDataBase()->Alias.Size; ++c) {
3491 //fprintf(stderr, "%u:%u: [%s]\n", f, c, it->GetDataBase()->Alias[c].CStr());
3492 if (s.CompareIgnoreCase(it->GetDataBase()->Alias[c]) == 0) {
3493 //fprintf(stderr, " FOUND!\n");
3494 return "tan";
3500 return "";
3502 //if (name == "type") return mVarType;
3503 ABORT("unknown variable: %s", name.CStr());
3504 return "";
3508 truth game::CheckDropLeftover (item *i) {
3509 if (i->IsBottle() && !ivanconfig::GetAutoDropBottles()) return false;
3510 if (i->IsCan() && !ivanconfig::GetAutoDropCans()) return false;
3511 if (!ivanconfig::GetAutoDropLeftOvers()) return false;
3512 return true;
3516 truth game::RunAllowScriptStr (cfestring &str) {
3517 truth res = true;
3518 if (str.GetSize() < 1) return true;
3519 //fprintf(stderr, "====\n%s\n====\n", str.CStr());
3520 inputfile *ifl = new meminputfile(str, &game::GetGlobalValueMap());
3521 ifl->setGetVarCB(game::ldrGetVar);
3522 mFEStack.push(ifl);
3523 res = DoOnEvent(true, true);
3524 //fprintf(stderr, "mFEStack: %u\n", mFEStack.size());
3525 return res;