save and bone files now can be compressed with ZLib (wow!)
[k8-i-v-a-n.git] / src / game / game.cpp
blob3392a8878d6f7671d3a6f521924fdd69aacc1064
1 /*
3 * Iter Vehemens ad Necem (IVAN)
4 * Copyright (C) Timo Kiviluoto
5 * Released under the GNU General
6 * Public License
8 * See LICENSING which should be included
9 * along with this file for more details
13 #include <algorithm>
14 #include <cstdarg>
16 #include <sys/stat.h>
17 #include <sys/types.h>
18 #ifdef WIN32
19 # include <unistd.h>
20 # include <windows.h>
21 #endif
23 #include "whandler.h"
24 #include "hscore.h"
25 #include "rawbit.h"
26 #include "message.h"
27 #include "feio.h"
28 #include "team.h"
29 #include "iconf.h"
30 #include "allocate.h"
31 #include "pool.h"
32 #include "god.h"
33 #include "proto.h"
34 #include "stack.h"
35 #include "felist.h"
36 #include "human.h"
37 #include "nonhuman.h"
38 #include "wsquare.h"
39 #include "game.h"
40 #include "graphics.h"
41 #include "bitmap.h"
42 #include "fesave.h"
43 #include "miscitem.h"
44 #include "room.h"
45 #include "materias.h"
46 #include "rain.h"
47 #include "gear.h"
48 #include "fetime.h"
49 #include "balance.h"
50 #include "confdef.h"
53 #define SAVE_FILE_VERSION 119 // Increment this if changes make savefiles incompatible
54 #define BONE_FILE_VERSION 106 // Increment this if changes make bonefiles incompatible
56 #define SAVE_FILE_VERSION 124 // Increment this if changes make savefiles incompatible
57 #define BONE_FILE_VERSION 109 // Increment this if changes make bonefiles incompatible
59 #define LOADED 0
60 #define NEW_GAME 1
61 #define BACK 2
64 std::stack<inputfile *> game::mFEStack;
65 character *game::mChar = 0;
66 ccharacter *game::mActor = 0;
67 ccharacter *game::mSecondActor = 0;
68 item *game::mItem = 0;
69 int game::mResult = 0;
72 int game::CurrentLevelIndex;
73 truth game::InWilderness = false;
74 worldmap* game::WorldMap;
75 area* game::AreaInLoad;
76 square* game::SquareInLoad;
77 dungeon** game::Dungeon;
78 int game::CurrentDungeonIndex;
79 feuLong game::NextCharacterID = 1;
80 feuLong game::NextItemID = 1;
81 feuLong game::NextTrapID = 1;
82 team** game::Team;
83 feuLong game::LOSTick;
84 v2 game::CursorPos(-1, -1);
85 truth game::Zoom;
86 truth game::Generating = false;
87 double game::AveragePlayerArmStrengthExperience;
88 double game::AveragePlayerLegStrengthExperience;
89 double game::AveragePlayerDexterityExperience;
90 double game::AveragePlayerAgilityExperience;
91 int game::Teams;
92 int game::Dungeons;
93 int game::StoryState;
94 /* */
95 int game::MondedrPass;
96 int game::RingOfThieves;
97 int game::Masamune;
98 int game::Muramasa;
99 int game::LoricatusHammer;
100 int game::Liberator;
101 /* */
102 massacremap game::PlayerMassacreMap;
103 massacremap game::PetMassacreMap;
104 massacremap game::MiscMassacreMap;
105 sLong game::PlayerMassacreAmount = 0;
106 sLong game::PetMassacreAmount = 0;
107 sLong game::MiscMassacreAmount = 0;
108 boneidmap game::BoneItemIDMap;
109 boneidmap game::BoneCharacterIDMap;
110 truth game::TooGreatDangerFoundTruth;
111 itemvectorvector game::ItemDrawVector;
112 charactervector game::CharacterDrawVector;
113 truth game::SumoWrestling;
114 liquid* game::GlobalRainLiquid;
115 v2 game::GlobalRainSpeed;
116 sLong game::GlobalRainTimeModifier;
117 truth game::PlayerSumoChampion;
118 truth game::PlayerSolicitusChampion;
119 feuLong game::SquarePartEmitationTick = 0;
120 sLong game::Turn;
121 truth game::PlayerRunning;
122 character* game::LastPetUnderCursor;
123 charactervector game::PetVector;
124 double game::DangerFound;
125 int game::OldAttribute[ATTRIBUTES];
126 int game::NewAttribute[ATTRIBUTES];
127 int game::LastAttributeChangeTick[ATTRIBUTES];
128 int game::NecroCounter;
129 int game::CursorData;
130 truth game::CausePanicFlag;
132 truth game::Loading = false;
133 truth game::JumpToPlayerBe = false;
134 truth game::InGetCommand = false;
135 character *game::Petrus = 0;
136 time_t game::TimePlayedBeforeLastLoad;
137 time_t game::LastLoad;
138 time_t game::GameBegan;
139 truth game::PlayerHasReceivedAllGodsKnownBonus;
141 festring game::AutoSaveFileName = game::GetSaveDir() + "AutoSave";
142 cchar *const game::Alignment[] = { "L++", "L+", "L", "L-", "N+", "N=", "N-", "C+", "C", "C-", "C--" };
143 god **game::God;
145 cint game::MoveNormalCommandKey[] = { KEY_HOME, KEY_UP, KEY_PAGE_UP, KEY_LEFT, KEY_RIGHT, KEY_END, KEY_DOWN, KEY_PAGE_DOWN, '.' };
146 int game::MoveAbnormalCommandKey[] = { '7','8','9','u','o','j','k','l','.' };
148 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) };
149 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) };
150 cv2 game::BasicMoveVector[] = { v2(-1, 0), v2(1, 0), v2(0, -1), v2(0, 1) };
151 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) };
152 cint game::LargeMoveDirection[] = { 0, 1, 1, 2, 3, 4, 3, 4, 5, 6, 6, 7, 8, 8, 8, 8 };
154 truth game::LOSUpdateRequested = false;
155 uChar ***game::LuxTable = 0;
156 truth game::Running;
157 character *game::Player;
158 v2 game::Camera(0, 0);
159 feuLong game::Tick;
160 gamescript *game::GameScript = 0;
161 valuemap game::GlobalValueMap;
162 dangermap game::DangerMap;
163 int game::NextDangerIDType;
164 int game::NextDangerIDConfigIndex;
165 characteridmap game::CharacterIDMap;
166 itemidmap game::ItemIDMap;
167 trapidmap game::TrapIDMap;
168 truth game::PlayerHurtByExplosion;
169 area *game::CurrentArea;
170 level *game::CurrentLevel;
171 wsquare ***game::CurrentWSquareMap;
172 lsquare ***game::CurrentLSquareMap;
173 festring game::DefaultPolymorphTo;
174 festring game::DefaultSummonMonster;
175 festring game::DefaultWish;
176 festring game::DefaultChangeMaterial;
177 festring game::DefaultDetectMaterial;
178 truth game::WizardMode;
179 int game::SeeWholeMapCheatMode;
180 truth game::GoThroughWallsCheat;
181 int game::QuestMonstersFound;
182 bitmap *game::BusyAnimationCache[32];
183 festring game::PlayerName;
184 feuLong game::EquipmentMemory[MAX_EQUIPMENT_SLOTS];
185 olterrain *game::MonsterPortal;
186 std::vector<v2> game::SpecialCursorPos;
187 std::vector<int> game::SpecialCursorData;
188 cbitmap *game::EnterImage;
189 v2 game::EnterTextDisplacement;
192 char game::GetAbnormalMoveKey (int idx) {
193 if (idx < 0 || idx > 8) return 0;
194 return MoveAbnormalCommandKey[idx];
198 void game::SetAbnormalMoveKey (int idx, char ch) {
199 if (idx >= 0 && idx <= 8) MoveAbnormalCommandKey[idx] = ch;
203 void game::AddCharacterID (character *Char, feuLong ID) {
204 /*k8:??? if (CharacterIDMap.find(ID) != CharacterIDMap.end())
205 int esko = esko = 2;*/
206 CharacterIDMap.insert(std::make_pair(ID, Char));
210 void game::RemoveCharacterID (feuLong ID) {
211 /*k8:??? if (CharacterIDMap.find(ID) == CharacterIDMap.end())
212 int esko = esko = 2;*/
213 CharacterIDMap.erase(CharacterIDMap.find(ID));
217 void game::AddItemID (item *Item, feuLong ID) {
218 /*k8:??? if (ItemIDMap.find(ID) != ItemIDMap.end())
219 int esko = esko = 2;*/
220 ItemIDMap.insert(std::make_pair(ID, Item));
224 void game::RemoveItemID (feuLong ID) {
225 /*k8:??? if(ID && ItemIDMap.find(ID) == ItemIDMap.end())
226 int esko = esko = 2;*/
227 if (ID) ItemIDMap.erase(ItemIDMap.find(ID));
231 void game::UpdateItemID (item *Item, feuLong ID) {
232 /*k8:??? if(ItemIDMap.find(ID) == ItemIDMap.end())
233 int esko = esko = 2;*/
234 ItemIDMap.find(ID)->second = Item;
238 void game::AddTrapID (entity *Trap, feuLong ID) {
239 /*k8:??? if(TrapIDMap.find(ID) != TrapIDMap.end())
240 int esko = esko = 2;*/
241 if (ID) TrapIDMap.insert(std::make_pair(ID, Trap));
245 void game::RemoveTrapID (feuLong ID) {
246 /*k8:??? if(ID && TrapIDMap.find(ID) == TrapIDMap.end())
247 int esko = esko = 2;*/
248 if (ID) TrapIDMap.erase(TrapIDMap.find(ID));
252 void game::UpdateTrapID (entity *Trap, feuLong ID) {
253 /*k8:??? if(TrapIDMap.find(ID) == TrapIDMap.end())
254 int esko = esko = 2;*/
255 TrapIDMap.find(ID)->second = Trap;
259 const dangermap &game::GetDangerMap () { return DangerMap; }
260 void game::ClearItemDrawVector () { ItemDrawVector.clear(); }
261 void game::ClearCharacterDrawVector () { CharacterDrawVector.clear(); }
264 void game::InitScript () {
265 inputfile ScriptFile(GetGameDir()+"Script/dungeon.dat", &GlobalValueMap);
266 GameScript = new gamescript;
267 GameScript->ReadFrom(ScriptFile);
268 { /* additional dungeon files */
269 for (int f = 0; f <= 99; f++) {
270 char bnum[32];
271 sprintf(bnum, "Script/dungeon_%02d.dat", f);
272 inputfile ifl(game::GetGameDir()+bnum, &game::GetGlobalValueMap(), false);
273 if (ifl.IsOpen()) {
274 //fprintf(stderr, "loading: %s\n", bnum+7);
275 GameScript->ReadFrom(ifl);
276 ifl.Close();
280 GameScript->RandomizeLevels();
284 truth game::Init (cfestring &Name) {
285 if (Name.IsEmpty()) {
286 if (ivanconfig::GetDefaultName().IsEmpty()) {
287 PlayerName.Empty();
288 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;
289 } else {
290 PlayerName = ivanconfig::GetDefaultName();
292 } else {
293 PlayerName = Name;
296 #ifndef WIN32
297 mkdir(GetSaveDir().CStr(), S_IRWXU|S_IRWXG);
298 mkdir(GetBoneDir().CStr(), S_IRWXU|S_IRWXG);
299 #else
300 mkdir(GetSaveDir().CStr());
301 mkdir(GetBoneDir().CStr());
302 #endif
304 LOSTick = 2;
305 DangerFound = 0;
306 CausePanicFlag = false;
307 //???
308 switch (Load(SaveName(PlayerName))) {
309 case LOADED: {
310 globalwindowhandler::InstallControlLoop(AnimationController);
311 SetIsRunning(true);
312 SetForceJumpToPlayerBe(true);
313 GetCurrentArea()->SendNewDrawRequest();
314 SendLOSUpdateRequest();
315 ADD_MESSAGE("Game loaded successfully.");
316 } return true;
317 case NEW_GAME: {
318 iosystem::TextScreen(CONST_S(
319 "You couldn't possibly have guessed this day would differ from any other.\n"
320 "It began just as always. You woke up at dawn and drove off the giant spider\n"
321 "resting on your face. On your way to work you had serious trouble avoiding\n"
322 "the lions and pythons roaming wild around the village. After getting kicked\n"
323 "by colony masters for being late you performed your twelve-hour routine of\n"
324 "climbing trees, gathering bananas, climbing trees, gathering bananas, chasing\n"
325 "monkeys that stole the first gathered bananas, carrying bananas to the village\n"
326 "and trying to look happy when real food was distributed.\n\n"
327 "Finally you were about to enjoy your free time by taking a quick dip in the\n"
328 "nearby crocodile bay. However, at this point something unusual happened.\n"
329 "You were summoned to the mansion of Richel Decos, the viceroy of the\n"
330 "colony, and were led directly to him."));
332 iosystem::TextScreen(CONST_S(
333 "\"I have a task for you, citizen\", said the viceroy picking his golden\n"
334 "teeth, \"The market price of bananas has taken a deep dive and yet the\n"
335 "central government is about to raise taxes. I have sent appeals to high\n"
336 "priest Petrus but received no response. I fear my enemies in Attnam are\n"
337 "plotting against me and intercepting my messages before they reach him!\"\n\n"
338 "\"That is why you must travel to Attnam with a letter I'll give you and\n"
339 "deliver it to Petrus directly. Alas, you somehow have to cross the sea\n"
340 "between. Because it's winter, all Attnamese ships are trapped by ice and\n"
341 "I have none. Therefore you must venture through the small underwater tunnel\n"
342 "connecting our islands. It is infested with monsters, but since you have\n"
343 "stayed alive here so long, the trip will surely cause you no trouble.\"\n\n"
344 "You have never been so happy! According to the mansion's traveling\n"
345 "brochures, Attnam is a peaceful but bustling world city on a beautiful\n"
346 "snowy fell surrounded by frozen lakes glittering in the arctic sun just\n"
347 "like the diamonds of the imperial treasury. Not that you would believe a\n"
348 "word. The point is that tomorrow you can finally forget your home and\n"
349 "face the untold adventures ahead."));
351 globalwindowhandler::InstallControlLoop(AnimationController);
352 SetIsRunning(true);
353 InWilderness = true;
354 iosystem::TextScreen(CONST_S("Generating game...\n\nThis may take some time, please wait."), ZERO_V2, WHITE, false, true, &BusyAnimation);
355 igraph::CreateBackGround(GRAY_FRACTAL);
356 NextCharacterID = 1;
357 NextItemID = 1;
358 NextTrapID = 1;
359 InitScript();
360 CreateTeams();
361 CreateGods();
362 SetPlayer(playerkind::Spawn());
363 Player->SetAssignedName(PlayerName);
364 Player->SetTeam(GetTeam(0));
365 Player->SetNP(SATIATED_LEVEL);
367 for (int c = 0; c < ATTRIBUTES; ++c) {
368 if (c != ENDURANCE) Player->EditAttribute(c, (RAND()&1)-(RAND()&1));
369 Player->EditExperience(c, 500, 1<<11);
371 Player->SetMoney(Player->GetMoney()+RAND()%11);
372 GetTeam(0)->SetLeader(Player);
373 InitDangerMap();
374 Petrus = 0;
375 InitDungeons();
376 SetCurrentArea(WorldMap = new worldmap(128, 128));
377 CurrentWSquareMap = WorldMap->GetMap();
378 WorldMap->Generate();
379 UpdateCamera();
380 SendLOSUpdateRequest();
381 Tick = 0;
382 Turn = 0;
383 InitPlayerAttributeAverage();
384 StoryState = 0;
385 /* */
386 MondedrPass = 0;
387 RingOfThieves = 0;
388 Masamune = 0;
389 Muramasa = 0;
390 LoricatusHammer = 0;
391 Liberator = 0;
392 /* */
393 PlayerMassacreMap.clear();
394 PetMassacreMap.clear();
395 MiscMassacreMap.clear();
396 PlayerMassacreAmount = PetMassacreAmount = MiscMassacreAmount = 0;
397 DefaultPolymorphTo.Empty();
398 DefaultSummonMonster.Empty();
399 DefaultWish.Empty();
400 DefaultChangeMaterial.Empty();
401 DefaultDetectMaterial.Empty();
402 Player->GetStack()->AddItem(encryptedscroll::Spawn());
403 if (ivanconfig::GetDefaultPetName() != "_none_") {
404 character *Doggie = dog::Spawn();
405 Doggie->SetTeam(GetTeam(0));
406 GetWorldMap()->GetPlayerGroup().push_back(Doggie);
407 Doggie->SetAssignedName(ivanconfig::GetDefaultPetName());
409 WizardMode = false;
410 SeeWholeMapCheatMode = MAP_HIDDEN;
411 GoThroughWallsCheat = false;
412 SumoWrestling = false;
413 GlobalRainTimeModifier = 2048-(RAND()&4095);
414 PlayerSumoChampion = false;
415 PlayerSolicitusChampion = false;
416 protosystem::InitCharacterDataBaseFlags();
417 memset(EquipmentMemory, 0, sizeof(EquipmentMemory));
418 PlayerRunning = false;
419 InitAttributeMemory();
420 NecroCounter = 0;
421 GameBegan = time(0);
422 LastLoad = time(0);
423 TimePlayedBeforeLastLoad = time::GetZeroTime();
424 /*k8: damn! seems that this is field, not local! bool PlayerHasReceivedAllGodsKnownBonus = false; */
425 PlayerHasReceivedAllGodsKnownBonus = false;
426 ADD_MESSAGE("You commence your journey to Attnam. Use direction keys to move, '>' to enter an area and '?' to view other commands.");
427 game::ClearEventData();
428 RunOnEvent("game_start");
429 if (IsXMas()) {
430 item *Present = banana::Spawn();
431 Player->GetStack()->AddItem(Present);
432 ADD_MESSAGE("Atavus is happy today! He gives you %s.", Present->CHAR_NAME(INDEFINITE));
434 } return true;
435 default: return false;
440 void game::DeInit () {
441 delete WorldMap;
442 WorldMap = 0;
443 int c;
444 for (c = 1; c < Dungeons; ++c) delete Dungeon[c];
445 delete [] Dungeon;
446 for (c = 1; c <= GODS; ++c) delete God[c]; // sorry, Valpuri!
447 delete [] God;
448 pool::BurnHell();
449 for (c = 0; c < Teams; ++c) delete Team[c];
450 delete [] Team;
451 delete GameScript;
452 msgsystem::Format();
453 DangerMap.clear();
457 void game::Run () {
458 for (;;) {
459 if (!InWilderness) {
460 /* Temporary places */
461 static int Counter = 0;
462 if (++Counter == 10) {
463 CurrentLevel->GenerateMonsters();
464 Counter = 0;
466 if (CurrentDungeonIndex == ELPURI_CAVE && CurrentLevelIndex == ZOMBIE_LEVEL && !RAND_N(1000+NecroCounter)) {
467 character *Char = necromancer::Spawn(RAND_N(4) ? APPRENTICE_NECROMANCER : MASTER_NECROMANCER);
468 v2 Pos;
469 for (int c2 = 0; c2 < 30; ++c2) {
470 Pos = GetCurrentLevel()->GetRandomSquare(Char);
471 if (abs(int(Pos.X)-Player->GetPos().X) > 20 || abs(int(Pos.Y)-Player->GetPos().Y) > 20) break;
473 if (Pos != ERROR_V2) {
474 Char->SetTeam(GetTeam(MONSTER_TEAM));
475 Char->PutTo(Pos);
476 Char->SetGenerationDanger(GetCurrentLevel()->GetDifficulty());
477 Char->SignalGeneration();
478 Char->SignalNaturalGeneration();
479 ivantime Time;
480 GetTime(Time);
481 int Modifier = Time.Day - EDIT_ATTRIBUTE_DAY_MIN;
482 if (Modifier > 0) Char->EditAllAttributes(Modifier >> EDIT_ATTRIBUTE_DAY_SHIFT);
483 NecroCounter += 50;
484 } else {
485 delete Char;
489 if (!(GetTick() % 1000)) CurrentLevel->CheckSunLight();
491 if ((CurrentDungeonIndex == NEW_ATTNAM || CurrentDungeonIndex == ATTNAM) && CurrentLevelIndex == 0) {
492 sLong OldVolume = GlobalRainLiquid->GetVolume();
493 sLong NewVolume = Max(sLong(sin((Tick+GlobalRainTimeModifier)*0.0003)*300-150), 0);
494 if (NewVolume && !OldVolume) CurrentLevel->EnableGlobalRain();
495 else if(!NewVolume && OldVolume) CurrentLevel->DisableGlobalRain();
496 GlobalRainLiquid->SetVolumeNoSignals(NewVolume);
499 item *Item;
500 if (!RAND_N(2)) Item = wand::Spawn(1 + RAND_N(12));
501 else if(!RAND_N(2)) {
502 Item = beartrap::Spawn();
503 Item->SetIsActive(true);
504 Item->SetTeam(MONSTER_TEAM);
505 } else if(!RAND_N(2)) {
506 Item = mine::Spawn();
507 Item->SetIsActive(true);
508 Item->SetTeam(MONSTER_TEAM);
509 } else Item = holybanana::Spawn();
510 CurrentLevel->GetLSquare(CurrentLevel->GetRandomSquare())->AddItem(Item);
513 if(!RAND_N(10)) {
514 character *Char = protosystem::CreateMonster(0, 1000000);
515 Char->ChangeTeam(GetTeam(RAND() % Teams));
516 Char->PutTo(CurrentLevel->GetRandomSquare(Char));
519 if (!RAND_N(5)) {
520 character *Char;
521 if (!RAND_N(5)) Char = spider::Spawn(GIANT);
522 else if (!RAND_N(5)) Char = darkmage::Spawn(1 + RAND_N(4));
523 else if (!RAND_N(5)) Char = necromancer::Spawn(1 + RAND_N(2));
524 else if (!RAND_N(5)) Char = chameleon::Spawn();
525 else if (!RAND_N(5)) Char = kamikazedwarf::Spawn(1 + RAND_N(GODS));
526 else if (!RAND_N(5)) Char = mommo::Spawn(1 + RAND_N(2));
527 else if (!RAND_N(3)) Char = bunny::Spawn(RAND_2 ? ADULT_MALE : ADULT_FEMALE);
528 else if (!RAND_N(3)) Char = eddy::Spawn();
529 else if (!RAND_N(3)) Char = magicmushroom::Spawn();
530 else if (!RAND_N(5)) Char = mushroom::Spawn();
531 else if (!RAND_N(3)) Char = blinkdog::Spawn();
532 else if (!RAND_N(5)) Char = tourist::Spawn(1 + RAND_N(3));
533 else if (!RAND_N(5)) Char = hattifattener::Spawn();
534 else if (!RAND_N(5)) Char = genetrixvesana::Spawn();
535 else if (!RAND_N(5)) Char = skunk::Spawn();
536 else if (!RAND_N(5)) Char = ennerbeast::Spawn();
537 else if (!RAND_N(5)) Char = werewolfhuman::Spawn();
538 else if (!RAND_N(5)) Char = unicorn::Spawn(1 + RAND_N(3));
539 else if (!RAND_N(5)) Char = floatingeye::Spawn();
540 else if (!RAND_N(5)) Char = zombie::Spawn();
541 else if (!RAND_N(5)) Char = magpie::Spawn();
542 else if (!RAND_N(5)) Char = elpuri::Spawn();
543 else if (!RAND_N(5)) Char = vladimir::Spawn();
544 else if (!RAND_N(5)) Char = billswill::Spawn();
545 else if (!RAND_N(5)) Char = ghost::Spawn();
546 else if (!RAND_N(5)) Char = dolphin::Spawn();
547 else if (!RAND_N(5)) Char = cossack::Spawn();
548 else Char = invisiblestalker::Spawn();
549 Char->SetTeam(GetTeam(RAND() % Teams));
550 Char->PutTo(CurrentLevel->GetRandomSquare(Char));
556 try {
557 pool::Be();
558 pool::BurnHell();
559 IncreaseTick();
560 ApplyDivineTick();
561 } catch (quitrequest) {
562 break;
563 } catch (areachangerequest) {
569 void game::InitLuxTable () {
570 if (!LuxTable) {
571 Alloc3D(LuxTable, 256, 33, 33);
572 for (int c = 0; c < 0x100; ++c)
573 for (int x = 0; x < 33; ++x)
574 for (int y = 0; y < 33; ++y) {
575 int X = x-16, Y = y-16;
576 LuxTable[c][x][y] = int(c/(double(X*X+Y*Y)/128+1));
578 atexit(DeInitLuxTable);
583 void game::DeInitLuxTable () {
584 delete [] LuxTable;
585 LuxTable = 0;
589 void game::UpdateCameraX () {
590 UpdateCameraX(Player->GetPos().X);
594 void game::UpdateCameraY () {
595 UpdateCameraY(Player->GetPos().Y);
599 void game::UpdateCameraX (int X) {
600 UpdateCameraCoordinate(Camera.X, X, GetCurrentArea()->GetXSize(), GetScreenXSize());
604 void game::UpdateCameraY (int Y) {
605 UpdateCameraCoordinate(Camera.Y, Y, GetCurrentArea()->GetYSize(), GetScreenYSize());
609 void game::UpdateCameraCoordinate (int &Coordinate, int Center, int Size, int ScreenSize) {
610 int OldCoordinate = Coordinate;
611 if (Size < ScreenSize) Coordinate = (Size-ScreenSize)>>1;
612 else if(Center < ScreenSize>>1) Coordinate = 0;
613 else if(Center > Size-(ScreenSize>>1)) Coordinate = Size-ScreenSize;
614 else Coordinate = Center-(ScreenSize>>1);
615 if (Coordinate != OldCoordinate) GetCurrentArea()->SendNewDrawRequest();
619 cchar *game::Insult () {
620 static const char *insults[19] = {
621 "moron",
622 "silly",
623 "idiot",
624 "airhead",
625 "jerk",
626 "dork",
627 "Mr. Mole",
628 "navastater",
629 "potatoes-for-eyes",
630 "lamer",
631 "mommo-for-brains",
632 "pinhead",
633 "stupid-headed person",
634 "software abuser",
635 "loser",
636 "peaballs",
637 "person-with-problems",
638 "unimportant user",
639 "hugger-mugger"
641 int n = RAND_N(18);
642 if (n < 0 || n > 18) n = 18;
643 return insults[n];
647 /* DefaultAnswer = REQUIRES_ANSWER the question requires an answer */
648 truth game::TruthQuestion (cfestring &String, int DefaultAnswer, int OtherKeyForTrue) {
649 if (DefaultAnswer == NO) DefaultAnswer = 'n';
650 else if (DefaultAnswer == YES) DefaultAnswer = 'y';
651 else if (DefaultAnswer != REQUIRES_ANSWER) ABORT("Illegal TruthQuestion DefaultAnswer send!");
652 int FromKeyQuestion = KeyQuestion(String, DefaultAnswer, 9, 'y', 'Y', 'n', 'N', 't', 'T', 'o', 'O', OtherKeyForTrue);
653 return
654 FromKeyQuestion == 'y' || FromKeyQuestion == 'Y' ||
655 FromKeyQuestion == 't' || FromKeyQuestion == 'T' ||
656 FromKeyQuestion == OtherKeyForTrue;
660 void game::DrawEverything () {
661 DrawEverythingNoBlit();
662 graphics::BlitDBToScreen();
666 truth game::OnScreen (v2 Pos) {
667 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();
671 void game::DrawEverythingNoBlit (truth AnimationDraw) {
672 if (LOSUpdateRequested && Player->IsEnabled()) {
673 if (!IsInWilderness()) GetCurrentLevel()->UpdateLOS(); else GetWorldMap()->UpdateLOS();
676 if (OnScreen(CursorPos)) {
677 if (!IsInWilderness() || CurrentWSquareMap[CursorPos.X][CursorPos.Y]->GetLastSeen() || GetSeeWholeMapCheatMode())
678 CurrentArea->GetSquare(CursorPos)->SendStrongNewDrawRequest();
679 else
680 DOUBLE_BUFFER->Fill(CalculateScreenCoordinates(CursorPos), TILE_V2, 0);
683 for (unsigned int c = 0; c < SpecialCursorPos.size(); ++c) {
684 if (OnScreen(SpecialCursorPos[c])) CurrentArea->GetSquare(SpecialCursorPos[c])->SendStrongNewDrawRequest();
687 globalwindowhandler::UpdateTick();
688 GetCurrentArea()->Draw(AnimationDraw);
689 Player->DrawPanel(AnimationDraw);
691 if (!AnimationDraw) msgsystem::Draw();
693 if (OnScreen(CursorPos)) {
694 v2 ScreenCoord = CalculateScreenCoordinates(CursorPos);
695 blitdata B = {
696 DOUBLE_BUFFER,
697 { 0, 0 },
698 { ScreenCoord.X, ScreenCoord.Y },
699 { TILE_SIZE, TILE_SIZE },
700 { 0 },
701 TRANSPARENT_COLOR,
702 ALLOW_ANIMATE|ALLOW_ALPHA
705 if (!IsInWilderness() && !GetSeeWholeMapCheatMode()) {
706 lsquare *Square = CurrentLSquareMap[CursorPos.X][CursorPos.Y];
707 if (Square->GetLastSeen() != GetLOSTick()) Square->DrawMemorized(B);
710 if (DoZoom()) {
711 B.Src = B.Dest;
712 B.Dest.X = RES.X - 96;
713 B.Dest.Y = RES.Y - 96;
714 B.Stretch = 5;
715 DOUBLE_BUFFER->StretchBlit(B);
718 igraph::DrawCursor(ScreenCoord, CursorData);
721 if (Player->IsEnabled()) {
722 if (Player->IsSmall()) {
723 v2 Pos = Player->GetPos();
724 if (OnScreen(Pos)) {
725 v2 ScreenCoord = CalculateScreenCoordinates(Pos);
726 igraph::DrawCursor(ScreenCoord, Player->GetCursorData());
728 } else {
729 for (int f = 0; f < Player->GetSquaresUnder(); ++f) {
730 v2 Pos = Player->GetPos(f);
731 if (OnScreen(Pos)) {
732 v2 ScreenCoord = CalculateScreenCoordinates(Pos);
733 igraph::DrawCursor(ScreenCoord, Player->GetCursorData()|CURSOR_BIG, f);
739 for (unsigned int c = 0; c < SpecialCursorPos.size(); ++c) {
740 if (OnScreen(SpecialCursorPos[c])) {
741 v2 ScreenCoord = CalculateScreenCoordinates(SpecialCursorPos[c]);
742 igraph::DrawCursor(ScreenCoord, SpecialCursorData[c]);
743 GetCurrentArea()->GetSquare(SpecialCursorPos[c])->SendStrongNewDrawRequest();
749 truth game::Save (cfestring &SaveName) {
750 if (!GetCurrentArea()->GetSquare(Player->GetPos())->GetCharacter()) {
751 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);
752 return false;
754 DrawEverythingNoBlit();
755 #if defined(SGAME_SHOTS_IPU) || !defined(HAVE_IMLIB2)
756 DOUBLE_BUFFER->SaveScaledIPU(SaveName+".ipu", 0.8); //640; 320
757 #else
758 DOUBLE_BUFFER->SaveScaledPNG(SaveName+".png", 0.8); //640; 320
759 #endif
760 outputfile SaveFile(SaveName+".sav");
761 SaveFile << int(SAVE_FILE_VERSION);
762 SaveFile << GameScript << CurrentDungeonIndex << CurrentLevelIndex << Camera;
763 SaveFile << WizardMode << SeeWholeMapCheatMode << GoThroughWallsCheat;
764 SaveFile << Tick << Turn << InWilderness << NextCharacterID << NextItemID << NextTrapID << NecroCounter;
765 SaveFile << SumoWrestling << PlayerSumoChampion << GlobalRainTimeModifier;
766 SaveFile << PlayerSolicitusChampion;
767 sLong Seed = RAND();
768 femath::SetSeed(Seed);
769 SaveFile << Seed;
770 SaveFile << AveragePlayerArmStrengthExperience;
771 SaveFile << AveragePlayerLegStrengthExperience;
772 SaveFile << AveragePlayerDexterityExperience;
773 SaveFile << AveragePlayerAgilityExperience;
774 SaveFile << Teams << Dungeons << StoryState << PlayerRunning;
775 SaveFile << MondedrPass << RingOfThieves << Masamune << Muramasa << LoricatusHammer << Liberator;
776 SaveFile << PlayerMassacreMap << PetMassacreMap << MiscMassacreMap;
777 SaveFile << PlayerMassacreAmount << PetMassacreAmount << MiscMassacreAmount;
778 SaveArray(SaveFile, EquipmentMemory, MAX_EQUIPMENT_SLOTS);
779 int c;
780 for (c = 0; c < ATTRIBUTES; ++c) SaveFile << OldAttribute[c] << NewAttribute[c] << LastAttributeChangeTick[c];
781 for (c = 1; c < Dungeons; ++c) SaveFile << Dungeon[c];
782 for (c = 1; c <= GODS; ++c) SaveFile << God[c];
783 for (c = 0; c < Teams; ++c) SaveFile << Team[c];
784 if (InWilderness) {
785 SaveWorldMap(SaveName, false);
786 } else {
787 GetCurrentDungeon()->SaveLevel(SaveName, CurrentLevelIndex, false);
789 SaveFile << Player->GetPos() << PlayerName;
790 msgsystem::Save(SaveFile);
791 SaveFile << DangerMap << NextDangerIDType << NextDangerIDConfigIndex;
792 SaveFile << DefaultPolymorphTo << DefaultSummonMonster;
793 SaveFile << DefaultWish << DefaultChangeMaterial << DefaultDetectMaterial;
794 SaveFile << GetTimeSpent();
795 /* or in more readable format: time() - LastLoad + TimeAtLastLoad */
796 SaveFile << PlayerHasReceivedAllGodsKnownBonus;
797 protosystem::SaveCharacterDataBaseFlags(SaveFile);
798 return true;
802 int game::Load (cfestring &SaveName) {
803 inputfile SaveFile(SaveName+".sav", 0, false);
804 if (!SaveFile.IsOpen()) return NEW_GAME;
805 int Version;
806 SaveFile >> Version;
807 if (Version != SAVE_FILE_VERSION) {
808 if (true || Version != 120) {
809 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))
810 return NEW_GAME;
811 else
812 return BACK;
815 SaveFile >> GameScript >> CurrentDungeonIndex >> CurrentLevelIndex >> Camera;
816 SaveFile >> WizardMode >> SeeWholeMapCheatMode >> GoThroughWallsCheat;
817 SaveFile >> Tick >> Turn >> InWilderness >> NextCharacterID >> NextItemID >> NextTrapID >> NecroCounter;
818 SaveFile >> SumoWrestling >> PlayerSumoChampion >> GlobalRainTimeModifier;
819 SaveFile >> PlayerSolicitusChampion;
820 femath::SetSeed(ReadType<sLong>(SaveFile));
821 SaveFile >> AveragePlayerArmStrengthExperience;
822 SaveFile >> AveragePlayerLegStrengthExperience;
823 SaveFile >> AveragePlayerDexterityExperience;
824 SaveFile >> AveragePlayerAgilityExperience;
825 SaveFile >> Teams >> Dungeons >> StoryState >> PlayerRunning;
826 SaveFile >> MondedrPass >> RingOfThieves >> Masamune >> Muramasa >> LoricatusHammer;
827 if (Version == SAVE_FILE_VERSION) {
828 SaveFile >> Liberator;
829 } else {
830 Liberator = 0;
832 SaveFile >> PlayerMassacreMap >> PetMassacreMap >> MiscMassacreMap;
833 SaveFile >> PlayerMassacreAmount >> PetMassacreAmount >> MiscMassacreAmount;
834 LoadArray(SaveFile, EquipmentMemory, MAX_EQUIPMENT_SLOTS);
835 int c;
836 for (c = 0; c < ATTRIBUTES; ++c) SaveFile >> OldAttribute[c] >> NewAttribute[c] >> LastAttributeChangeTick[c];
837 Dungeon = new dungeon*[Dungeons];
838 Dungeon[0] = 0;
839 for (c = 1; c < Dungeons; ++c) SaveFile >> Dungeon[c];
840 God = new god*[GODS+1];
841 God[0] = 0;
842 for (c = 1; c <= GODS; ++c) SaveFile >> God[c];
843 Team = new team*[Teams];
844 for (c = 0; c < Teams; ++c) SaveFile >> Team[c];
845 if (InWilderness) {
846 SetCurrentArea(LoadWorldMap(SaveName));
847 CurrentWSquareMap = WorldMap->GetMap();
848 igraph::CreateBackGround(GRAY_FRACTAL);
849 } else {
850 SetCurrentArea(CurrentLevel = GetCurrentDungeon()->LoadLevel(SaveName, CurrentLevelIndex));
851 CurrentLSquareMap = CurrentLevel->GetMap();
852 igraph::CreateBackGround(*CurrentLevel->GetLevelScript()->GetBackGroundType());
854 v2 Pos;
855 SaveFile >> Pos >> PlayerName;
856 SetPlayer(GetCurrentArea()->GetSquare(Pos)->GetCharacter());
857 if (!PLAYER) {
858 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))
859 return NEW_GAME;
860 else
861 return BACK;
863 msgsystem::Load(SaveFile);
864 SaveFile >> DangerMap >> NextDangerIDType >> NextDangerIDConfigIndex;
865 SaveFile >> DefaultPolymorphTo >> DefaultSummonMonster;
866 SaveFile >> DefaultWish >> DefaultChangeMaterial >> DefaultDetectMaterial;
867 SaveFile >> TimePlayedBeforeLastLoad;
868 SaveFile >> PlayerHasReceivedAllGodsKnownBonus;
869 LastLoad = time(0);
870 protosystem::LoadCharacterDataBaseFlags(SaveFile);
871 return LOADED;
875 festring game::SaveName (cfestring &Base) {
876 festring SaveName = GetSaveDir();
877 if (!Base.GetSize()) SaveName << PlayerName; else SaveName << Base;
878 for (festring::sizetype c = 0; c < SaveName.GetSize(); ++c) if (SaveName[c] == ' ') SaveName[c] = '_';
879 return SaveName;
883 int game::GetMoveCommandKeyBetweenPoints (v2 A, v2 B) {
884 for (int c = 0; c < EXTENDED_DIRECTION_COMMAND_KEYS; ++c) {
885 if ((A + GetMoveVector(c)) == B) return GetMoveCommandKey(c);
887 return DIR_ERROR;
891 void game::ApplyDivineTick () {
892 for (int c = 1; c <= GODS; ++c) GetGod(c)->ApplyDivineTick();
896 void game::ApplyDivineAlignmentBonuses (god *CompareTarget, int Multiplier, truth Good) {
897 for (int c = 1; c <= GODS; ++c) if (GetGod(c) != CompareTarget) GetGod(c)->AdjustRelation(CompareTarget, Multiplier, Good);
901 v2 game::GetDirectionVectorForKey (int Key) {
902 if (Key == KEY_NUMPAD_5 || Key == '.') return ZERO_V2; /* k8: '.' */
903 for (int c = 0; c < EXTENDED_DIRECTION_COMMAND_KEYS; ++c) if (Key == GetMoveCommandKey(c)) return GetMoveVector(c);
904 return ERROR_V2;
908 double game::GetMinDifficulty () {
909 double Base = CurrentLevel->GetDifficulty()*0.2;
910 sLong MultiplierExponent = 0;
911 ivantime Time;
912 GetTime(Time);
913 int Modifier = Time.Day-DANGER_PLUS_DAY_MIN;
914 if (Modifier > 0) Base += DANGER_PLUS_MULTIPLIER * Modifier;
915 for (;;) {
916 int Dice = RAND()%25;
917 if (Dice < 5 && MultiplierExponent > -3) {
918 Base /= 3;
919 --MultiplierExponent;
920 continue;
922 if (Dice >= 20 && MultiplierExponent < 3) {
923 Base *= 3;
924 ++MultiplierExponent;
925 continue;
927 return Base;
932 void game::ShowLevelMessage () {
933 if (CurrentLevel->GetLevelMessage().GetSize()) ADD_MESSAGE("%s", CurrentLevel->GetLevelMessage().CStr());
934 CurrentLevel->SetLevelMessage("");
938 int game::DirectionQuestion (cfestring &Topic, truth RequireAnswer, truth AcceptYourself) {
939 for (;;) {
940 int Key = AskForKeyPress(Topic);
941 if (AcceptYourself && (Key == '.' || Key == KEY_NUMPAD_5)) return YOURSELF; //k8
942 for (int c = 0; c < DIRECTION_COMMAND_KEYS; ++c) if (Key == GetMoveCommandKey(c)) return c;
943 if (!RequireAnswer) return DIR_ERROR;
948 void game::RemoveSaves (truth RealSavesAlso) {
949 if (RealSavesAlso) {
950 remove(festring(SaveName()+".sav").CStr());
951 remove(festring(SaveName()+".wm").CStr());
952 remove(festring(SaveName()+".png").CStr());
953 remove(festring(SaveName()+".ipu").CStr());
955 remove(festring(AutoSaveFileName+".sav").CStr());
956 remove(festring(AutoSaveFileName+".wm").CStr());
957 remove(festring(AutoSaveFileName+".png").CStr());
958 remove(festring(AutoSaveFileName+".ipu").CStr());
959 festring File;
960 for (int i = 1; i < Dungeons; ++i) {
961 for (int c = 0; c < GetDungeon(i)->GetLevels(); ++c) {
962 /* This looks very odd. And it is very odd.
963 * Indeed, gcc is very odd to not compile this correctly with -O3
964 * if it is written in a less odd way. */
965 File = SaveName()+'.'+i;
966 File << c;
967 if (RealSavesAlso) remove(File.CStr());
968 File = AutoSaveFileName+'.'+i;
969 File << c;
970 remove(File.CStr());
976 void game::SetPlayer (character *NP) {
977 Player = NP;
978 if (Player) Player->AddFlags(C_PLAYER);
982 void game::InitDungeons () {
983 Dungeons = *GetGameScript()->GetDungeons()+1;
984 Dungeon = new dungeon*[Dungeons];
985 Dungeon[0] = 0;
986 for (int c = 1; c < Dungeons; ++c) {
987 Dungeon[c] = new dungeon(c);
988 Dungeon[c]->SetIndex(c);
993 void game::DoEvilDeed (int Amount) {
994 if (!Amount) return;
995 for (int c = 1; c <= GODS; ++c) {
996 int Change = Amount-Amount*GetGod(c)->GetAlignment()/5;
997 if (!IsInWilderness() && Player->GetLSquareUnder()->GetDivineMaster() == c) {
998 if (GetGod(c)->GetRelation()-(Change << 1) < -750) {
999 if (GetGod(c)->GetRelation() > -750) GetGod(c)->SetRelation(-750);
1000 } else if (GetGod(c)->GetRelation()-(Change << 1) > 750) {
1001 if (GetGod(c)->GetRelation() < 750) GetGod(c)->SetRelation(750);
1002 } else GetGod(c)->SetRelation(GetGod(c)->GetRelation()-(Change << 1));
1003 } else {
1004 if(GetGod(c)->GetRelation()-Change < -500) {
1005 if (GetGod(c)->GetRelation() > -500) GetGod(c)->SetRelation(-500);
1006 } else if (GetGod(c)->GetRelation()-Change > 500) {
1007 if (GetGod(c)->GetRelation() < 500) GetGod(c)->SetRelation(500);
1008 } else GetGod(c)->SetRelation(GetGod(c)->GetRelation() - Change);
1014 void game::SaveWorldMap (cfestring &SaveName, truth DeleteAfterwards) {
1015 outputfile SaveFile(SaveName+".wm");
1016 SaveFile << WorldMap;
1017 if (DeleteAfterwards) {
1018 delete WorldMap;
1019 WorldMap = 0;
1024 worldmap *game::LoadWorldMap (cfestring &SaveName) {
1025 inputfile SaveFile(SaveName+".wm");
1026 SaveFile >> WorldMap;
1027 return WorldMap;
1031 void game::Hostility (team *Attacker, team *Defender) {
1032 for (int c = 0; c < Teams; ++c) {
1033 if (GetTeam(c) != Attacker && GetTeam(c) != Defender &&
1034 GetTeam(c)->GetRelation(Defender) == FRIEND &&
1035 c != NEW_ATTNAM_TEAM && c != TOURIST_GUIDE_TEAM) // gum solution
1036 GetTeam(c)->SetRelation(Attacker, HOSTILE);
1041 void game::CreateTeams () {
1042 Teams = *GetGameScript()->GetTeams();
1043 Team = new team*[Teams];
1044 int c;
1045 for (c = 0; c < Teams; ++c) {
1046 Team[c] = new team(c);
1047 for (int i = 0; i < c; ++i) Team[i]->SetRelation(Team[c], UNCARING);
1049 for (c = 0; c < Teams; ++c) if (c != 1) Team[1]->SetRelation(Team[c], HOSTILE);
1050 const std::list<std::pair<int, teamscript> >& TeamScript = GetGameScript()->GetTeam();
1051 for (std::list<std::pair<int, teamscript> >::const_iterator i = TeamScript.begin(); i != TeamScript.end(); ++i) {
1052 for (uInt c = 0; c < i->second.GetRelation().size(); ++c) {
1053 GetTeam(i->second.GetRelation()[c].first)->SetRelation(GetTeam(i->first), i->second.GetRelation()[c].second);
1055 cint *KillEvilness = i->second.GetKillEvilness();
1056 if (KillEvilness) GetTeam(i->first)->SetKillEvilness(*KillEvilness);
1061 /* v2 Pos should be removed from xxxQuestion()s? */
1062 festring game::StringQuestion (cfestring &Topic, col16 Color, festring::sizetype MinLetters, festring::sizetype MaxLetters, truth AllowExit, stringkeyhandler KeyHandler) {
1063 DrawEverythingNoBlit();
1064 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23)); // pos may be incorrect!
1065 festring Return;
1066 iosystem::StringQuestion(Return, Topic, v2(16, 6), Color, MinLetters, MaxLetters, false, AllowExit, KeyHandler);
1067 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1068 return Return;
1072 sLong game::NumberQuestion (cfestring &Topic, col16 Color, truth ReturnZeroOnEsc) {
1073 DrawEverythingNoBlit();
1074 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1075 sLong Return = iosystem::NumberQuestion(Topic, v2(16, 6), Color, false, ReturnZeroOnEsc);
1076 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1077 return Return;
1081 sLong game::ScrollBarQuestion (cfestring &Topic, sLong BeginValue, sLong Step, sLong Min, sLong Max, sLong AbortValue, col16 TopicColor, col16 Color1, col16 Color2, void (*Handler)(sLong)) {
1082 DrawEverythingNoBlit();
1083 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1084 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);
1085 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize() << 4, 23));
1086 return Return;
1090 feuLong game::IncreaseLOSTick () {
1091 if (LOSTick != 0xFE) return LOSTick += 2;
1092 CurrentLevel->InitLastSeen();
1093 return LOSTick = 4;
1097 void game::UpdateCamera () {
1098 UpdateCameraX();
1099 UpdateCameraY();
1103 truth game::HandleQuitMessage () {
1104 if (IsRunning()) {
1105 if (IsInGetCommand()) {
1106 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)) {
1107 case 0:
1108 Save();
1109 RemoveSaves(false);
1110 break;
1111 case 2:
1112 GetCurrentArea()->SendNewDrawRequest();
1113 DrawEverything();
1114 return false;
1115 default:
1116 festring Msg = CONST_S("cowardly quit the game");
1117 Player->AddScoreEntry(Msg, 0.75);
1118 End(Msg, true, false);
1119 break;
1121 } 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)) {
1122 RemoveSaves();
1123 } else {
1124 GetCurrentArea()->SendNewDrawRequest();
1125 DrawEverything();
1126 return false;
1129 return true;
1133 int game::GetDirectionForVector (v2 Vector) {
1134 for (int c = 0; c < DIRECTION_COMMAND_KEYS; ++c) if (Vector == GetMoveVector(c)) return c;
1135 return DIR_ERROR;
1139 cchar *game::GetVerbalPlayerAlignment () {
1140 sLong Sum = 0;
1141 for (int c = 1; c <= GODS; ++c) {
1142 if (GetGod(c)->GetRelation() > 0) Sum += GetGod(c)->GetRelation() * (5 - GetGod(c)->GetAlignment());
1144 if (Sum > 15000) return "extremely lawful";
1145 if (Sum > 10000) return "very lawful";
1146 if (Sum > 5000) return "lawful";
1147 if (Sum > 1000) return "mildly lawful";
1148 if (Sum > -1000) return "neutral";
1149 if (Sum > -5000) return "mildly chaotic";
1150 if (Sum > -10000) return "chaotic";
1151 if (Sum > -15000) return "very chaotic";
1152 return "extremely chaotic";
1156 void game::CreateGods () {
1157 God = new god*[GODS+1];
1158 God[0] = 0;
1159 for (int c = 1; c < protocontainer<god>::GetSize(); ++c) God[c] = protocontainer<god>::GetProto(c)->Spawn();
1163 void game::BusyAnimation () {
1164 BusyAnimation(DOUBLE_BUFFER, false);
1168 void game::BusyAnimation (bitmap *Buffer, truth ForceDraw) {
1169 static clock_t LastTime = 0;
1170 static int Frame = 0;
1171 static blitdata B1 = {
1173 { 0, 0 },
1174 { 0, 0 },
1175 { RES.X, RES.Y },
1176 { 0 },
1180 static blitdata B2 = {
1182 { 0, 0 },
1183 { (RES.X >> 1) - 100, (RES.Y << 1) / 3 - 100 },
1184 { 200, 200 },
1185 { 0 },
1189 if (ForceDraw || clock()-LastTime > CLOCKS_PER_SEC/25) {
1190 B2.Bitmap = Buffer;
1191 B2.Dest.X = (RES.X>>1)-100+EnterTextDisplacement.X;
1192 B2.Dest.Y = (RES.Y<<1)/3-100+EnterTextDisplacement.Y;
1193 if (EnterImage) {
1194 B1.Bitmap = Buffer;
1195 EnterImage->NormalMaskedBlit(B1);
1197 BusyAnimationCache[Frame]->NormalBlit(B2);
1198 if (Buffer == DOUBLE_BUFFER) graphics::BlitDBToScreen();
1199 if (++Frame == 32) Frame = 0;
1200 LastTime = clock();
1205 void game::CreateBusyAnimationCache () {
1206 bitmap Elpuri(TILE_V2, TRANSPARENT_COLOR);
1207 Elpuri.ActivateFastFlag();
1208 packcol16 Color = MakeRGB16(60, 60, 60);
1209 igraph::GetCharacterRawGraphic()->MaskedBlit(&Elpuri, v2(64, 0), ZERO_V2, TILE_V2, &Color);
1210 bitmap Circle(v2(200, 200), TRANSPARENT_COLOR);
1211 Circle.ActivateFastFlag();
1212 for (int x = 0; x < 4; ++x) Circle.DrawPolygon(100, 100, 95+x, 50, MakeRGB16(255-12*x, 0, 0));
1213 blitdata B1 = {
1215 { 0, 0 },
1216 { 92, 92 },
1217 { TILE_SIZE, TILE_SIZE },
1218 { 0 },
1219 TRANSPARENT_COLOR,
1222 blitdata B2 = {
1224 { 0, 0 },
1225 { 0, 0 },
1226 { 200, 200 },
1227 { 0 },
1228 TRANSPARENT_COLOR,
1231 for (int c = 0; c < 32; ++c) {
1232 B1.Bitmap = B2.Bitmap = BusyAnimationCache[c] = new bitmap(v2(200, 200), 0);
1233 B1.Bitmap->ActivateFastFlag();
1234 Elpuri.NormalMaskedBlit(B1);
1235 double Rotation = 0.3+c*FPI/80;
1236 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);
1237 Circle.NormalMaskedBlit(B2);
1242 int game::AskForKeyPress (cfestring &Topic) {
1243 DrawEverythingNoBlit();
1244 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Topic.CapitalizeCopy().CStr());
1245 graphics::BlitDBToScreen();
1246 int Key = GET_KEY();
1247 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1248 return Key;
1252 void game::AskForEscPress (cfestring &Topic) {
1253 DrawEverythingNoBlit();
1254 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), RED/*WHITE*/, "%s [press ESC]", Topic.CapitalizeCopy().CStr());
1255 graphics::BlitDBToScreen();
1256 int Key;
1257 do {
1258 Key = GET_KEY();
1259 } while (Key != KEY_ESC);
1260 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1264 /* Handler is called when the key has been identified as a movement key
1265 * KeyHandler is called when the key has NOT been identified as a movement key
1266 * Both can be deactivated by passing 0 as parameter */
1267 v2 game::PositionQuestion (cfestring &Topic, v2 CursorPos, void (*Handler)(v2), positionkeyhandler KeyHandler, truth Zoom) {
1268 int Key = 0;
1269 SetDoZoom(Zoom);
1270 v2 Return;
1271 CursorData = RED_CURSOR;
1272 if (Handler) Handler(CursorPos);
1273 for (;;) {
1274 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1275 if (!Square->HasBeenSeen() &&
1276 (!Square->GetCharacter() || !Square->GetCharacter()->CanBeSeenByPlayer()) &&
1277 !GetSeeWholeMapCheatMode()) DOUBLE_BUFFER->Fill(CalculateScreenCoordinates(CursorPos), TILE_V2, BLACK);
1278 else GetCurrentArea()->GetSquare(CursorPos)->SendStrongNewDrawRequest();
1280 if (Key == ' ' || Key == '.' || Key == KEY_NUMPAD_5) { Return = CursorPos; break; }
1281 if (Key == KEY_ESC) { Return = ERROR_V2; break; }
1283 v2 DirectionVector = GetDirectionVectorForKey(Key);
1284 if (DirectionVector != ERROR_V2) {
1285 CursorPos += DirectionVector;
1286 if (CursorPos.X > GetCurrentArea()->GetXSize()-1) CursorPos.X = 0;
1287 if (CursorPos.X < 0) CursorPos.X = GetCurrentArea()->GetXSize()-1;
1288 if (CursorPos.Y > GetCurrentArea()->GetYSize()-1) CursorPos.Y = 0;
1289 if (CursorPos.Y < 0) CursorPos.Y = GetCurrentArea()->GetYSize()-1;
1290 if (Handler) Handler(CursorPos);
1291 } else if (KeyHandler) {
1292 CursorPos = KeyHandler(CursorPos, Key);
1293 if (CursorPos == ERROR_V2 || CursorPos == ABORT_V2) {
1294 Return = CursorPos;
1295 break;
1299 if (ivanconfig::GetAutoCenterMapOnLook()) {
1300 UpdateCameraX(CursorPos.X);
1301 UpdateCameraY(CursorPos.Y);
1302 } else {
1303 if (CursorPos.X < GetCamera().X+3 || CursorPos.X >= GetCamera().X+GetScreenXSize()-3) UpdateCameraX(CursorPos.X);
1304 if (CursorPos.Y < GetCamera().Y+3 || CursorPos.Y >= GetCamera().Y+GetScreenYSize()-3) UpdateCameraY(CursorPos.Y);
1307 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Topic.CStr());
1308 SetCursorPos(CursorPos);
1309 DrawEverything();
1310 Key = GET_KEY();
1313 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1314 igraph::BlitBackGround(v2(RES.X-96, RES.Y-96), v2(80, 80));
1315 SetDoZoom(false);
1316 SetCursorPos(v2(-1, -1));
1317 return Return;
1321 void game::LookHandler (v2 CursorPos) {
1322 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1323 festring OldMemory;
1325 if (GetSeeWholeMapCheatMode()) {
1326 OldMemory = Square->GetMemorizedDescription();
1327 if (IsInWilderness()) GetWorldMap()->GetWSquare(CursorPos)->UpdateMemorizedDescription(true);
1328 else GetCurrentLevel()->GetLSquare(CursorPos)->UpdateMemorizedDescription(true);
1331 festring Msg;
1332 if (Square->HasBeenSeen() || GetSeeWholeMapCheatMode()) {
1333 if (!IsInWilderness() && !Square->CanBeSeenByPlayer() && GetCurrentLevel()->GetLSquare(CursorPos)->CanBeFeltByPlayer())
1334 Msg = CONST_S("You feel here ");
1335 else if (Square->CanBeSeenByPlayer(true) || GetSeeWholeMapCheatMode())
1336 Msg = CONST_S("You see here ");
1337 else
1338 Msg = CONST_S("You remember here ");
1339 Msg << Square->GetMemorizedDescription() << '.';
1340 if (!IsInWilderness() && (Square->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) {
1341 lsquare *LSquare = GetCurrentLevel()->GetLSquare(CursorPos);
1342 LSquare->DisplaySmokeInfo(Msg);
1343 if (LSquare->HasEngravings() && LSquare->IsTransparent()) {
1344 if (LSquare->EngravingsCanBeReadByPlayer() || GetSeeWholeMapCheatMode()) LSquare->DisplayEngravedInfo(Msg);
1345 else Msg << " Something has been engraved here.";
1348 } else Msg = CONST_S("You have never been here.");
1349 character *Character = Square->GetCharacter();
1350 if (Character && (Character->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) Character->DisplayInfo(Msg);
1351 if (!(RAND()%10000) && (Square->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode())) Msg << " You see here a frog eating a magnolia.";
1352 ADD_MESSAGE("%s", Msg.CStr());
1353 if (GetSeeWholeMapCheatMode()) Square->SetMemorizedDescription(OldMemory);
1357 truth game::AnimationController () {
1358 DrawEverythingNoBlit(true);
1359 return true;
1363 void game::LoadGlobalValueMap (inputfile &fl) {
1364 festring word;
1365 fl.setGetVarCB(game::ldrGetVar);
1366 for (fl.ReadWord(word, false); !fl.Eof(); fl.ReadWord(word, false)) {
1367 if (word == "Include") {
1368 word = fl.ReadWord();
1369 if (fl.ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl.GetFileName().CStr(), fl.TellLine());
1370 //fprintf(stderr, "loading: %s\n", word.CStr());
1371 inputfile incf(game::GetGameDir()+"Script/"+word, &game::GetGlobalValueMap());
1372 LoadGlobalValueMap(incf);
1373 continue;
1375 if (word == "Message") {
1376 word = fl.ReadWord();
1377 if (fl.ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl.GetFileName().CStr(), fl.TellLine());
1378 fprintf(stderr, "MESSAGE: %s\n", word.CStr());
1379 continue;
1381 if (word != "#") ABORT("Illegal datafile define in file %s on line %d!", fl.GetFileName().CStr(), fl.TellLine());
1382 fl.ReadWord(word, true);
1383 if (word == "enum" || word == "bitenum") {
1384 truth isBit = word == "bitenum";
1385 sLong idx = 0;
1386 if (fl.ReadWord() != "{") ABORT("'{' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TellLine());
1387 festring idName;
1388 truth done = false;
1389 while (!done) {
1390 fl.ReadWord(word, true);
1391 if (word == "}") break;
1392 if (word == "=") {
1393 idName.Empty();
1394 } else {
1395 idName = word;
1396 fl.ReadWord(word, true);
1398 if (word == "=") {
1399 // set current index
1400 idx = fl.ReadNumber();
1401 } else {
1402 if (word != "," && word != ";" && word != "}") ABORT("',' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TellLine());
1403 if (word == "}") done = true;
1405 if (idName.GetSize() > 0) {
1406 sLong i = idx;
1407 if (isBit) i = 1<<i;
1408 GlobalValueMap.insert(std::make_pair(idName, i));
1409 idx++;
1412 fl.SkipSpaces();
1413 int ch = fl.Get();
1414 if (ch != EOF && ch != ';') fl.Unget(ch);
1415 //if (fl.ReadWord() != ";") ABORT("';' expected in file %s at line %d!", fl.GetFileName().CStr(), fl.TellLine());
1416 continue;
1418 if (word == "define") {
1419 fl.ReadWord(word);
1420 sLong v = fl.ReadNumber();
1421 GlobalValueMap.insert(std::make_pair(word, v));
1422 continue;
1424 ABORT("Illegal datafile define in file %s on line %d!", fl.GetFileName().CStr(), fl.TellLine());
1429 void game::InitGlobalValueMap () {
1430 inputfile SaveFile(GetGameDir()+"Script/define.dat", &GlobalValueMap);
1431 LoadGlobalValueMap(SaveFile);
1432 { /* additional files */
1433 for (int f = 0; f <= 99; f++) {
1434 char bnum[32];
1435 sprintf(bnum, "Script/define_%02d.dat", f);
1436 festring fn = game::GetGameDir();
1437 fn << bnum;
1438 if (inputfile::fileExists(fn)) return;
1439 inputfile ifl(fn, &game::GetGlobalValueMap(), false);
1440 if (ifl.IsOpen()) {
1441 LoadGlobalValueMap(ifl);
1442 ifl.Close();
1449 void game::TextScreen (cfestring &Text, v2 Displacement, col16 Color, truth GKey, truth Fade, bitmapeditor BitmapEditor) {
1450 globalwindowhandler::DisableControlLoops();
1451 iosystem::TextScreen(Text, Displacement, Color, GKey, Fade, BitmapEditor);
1452 globalwindowhandler::EnableControlLoops();
1456 /* ... all the keys that are acceptable
1457 DefaultAnswer = REQUIRES_ANSWER if this question requires an answer
1458 Not surprisingly KeyNumber is the number of keys at ...
1460 int game::KeyQuestion (cfestring &Message, int DefaultAnswer, int KeyNumber, ...) {
1461 int *Key = new int[KeyNumber];
1462 va_list Arguments;
1463 va_start(Arguments, KeyNumber);
1464 for (int c = 0; c < KeyNumber; ++c) Key[c] = va_arg(Arguments, int);
1465 va_end(Arguments);
1466 DrawEverythingNoBlit();
1467 FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Message.CStr());
1468 graphics::BlitDBToScreen();
1469 int Return = 0;
1470 while (!Return) {
1471 int k = GET_KEY();
1472 for (int c = 0; c < KeyNumber; ++c) {
1473 if (Key[c] == k) {
1474 Return = k;
1475 break;
1478 if (!Return && DefaultAnswer != REQUIRES_ANSWER) Return = DefaultAnswer;
1480 delete [] Key;
1481 igraph::BlitBackGround(v2(16, 6), v2(GetScreenXSize()<<4, 23));
1482 return Return;
1486 v2 game::LookKeyHandler (v2 CursorPos, int Key) {
1487 square *Square = GetCurrentArea()->GetSquare(CursorPos);
1488 switch (Key) {
1489 case 'i':
1490 if (!IsInWilderness()) {
1491 if (Square->CanBeSeenByPlayer() || CursorPos == Player->GetPos() || GetSeeWholeMapCheatMode()) {
1492 lsquare *LSquare = GetCurrentLevel()->GetLSquare(CursorPos);
1493 stack *Stack = LSquare->GetStack();
1494 if (LSquare->IsTransparent() && Stack->GetVisibleItems(Player))
1495 Stack->DrawContents(Player, "Items here", NO_SELECT|(GetSeeWholeMapCheatMode() ? 0 : NO_SPECIAL_INFO));
1496 else
1497 ADD_MESSAGE("You see no items here.");
1498 } else ADD_MESSAGE("You should perhaps move a bit closer.");
1500 break;
1501 case 'c':
1502 if (Square->CanBeSeenByPlayer() || CursorPos == Player->GetPos() || GetSeeWholeMapCheatMode()) {
1503 character *Char = Square->GetCharacter();
1504 if (Char && (Char->CanBeSeenByPlayer() || Char->IsPlayer() || GetSeeWholeMapCheatMode()))
1505 Char->PrintInfo();
1506 else
1507 ADD_MESSAGE("You see no one here.");
1508 } else ADD_MESSAGE("You should perhaps move a bit closer.");
1509 break;
1511 return CursorPos;
1515 v2 game::NameKeyHandler (v2 CursorPos, int Key) {
1516 if (SelectPet(Key)) return LastPetUnderCursor->GetPos();
1517 if (Key == 'n' || Key == 'N') {
1518 character *Char = GetCurrentArea()->GetSquare(CursorPos)->GetCharacter();
1519 if (Char && Char->CanBeSeenByPlayer()) Char->TryToName();
1520 else ADD_MESSAGE("You don't see anyone here to name.");
1522 return CursorPos;
1526 void game::End (festring DeathMessage, truth Permanently, truth AndGoToMenu) {
1527 globalwindowhandler::DeInstallControlLoop(AnimationController);
1528 SetIsRunning(false);
1529 if (Permanently || !WizardModeIsReallyActive()) RemoveSaves(Permanently);
1530 if (Permanently && !WizardModeIsReallyActive()) {
1531 highscore HScore;
1532 if (HScore.LastAddFailed()) {
1533 iosystem::TextScreen(CONST_S("You didn't manage to get onto the high score list.\n\n\n\n")+GetPlayerName()+", "+DeathMessage+"\nRIP");
1534 } else HScore.Draw();
1536 if (AndGoToMenu) {
1537 /* This prevents monster movement etc. after death. */
1538 throw quitrequest();
1543 int game::CalculateRoughDirection (v2 Vector) {
1544 if (!Vector.X && !Vector.Y) return YOURSELF;
1545 double Angle = femath::CalculateAngle(Vector);
1546 if (Angle < FPI / 8) return 4;
1547 else if (Angle < 3*FPI/8) return 7;
1548 else if (Angle < 5*FPI/8) return 6;
1549 else if (Angle < 7*FPI/8) return 5;
1550 else if (Angle < 9*FPI/8) return 3;
1551 else if (Angle < 11*FPI/8) return 0;
1552 else if (Angle < 13*FPI/8) return 1;
1553 else if (Angle < 15*FPI/8) return 2;
1554 else return 4;
1558 int game::Menu (bitmap *BackGround, v2 Pos, cfestring &Topic, cfestring &sMS, col16 Color, cfestring &SmallText1, cfestring &SmallText2) {
1559 globalwindowhandler::DisableControlLoops();
1560 int Return = iosystem::Menu(BackGround, Pos, Topic, sMS, Color, SmallText1, SmallText2);
1561 globalwindowhandler::EnableControlLoops();
1562 return Return;
1566 void game::InitDangerMap () {
1567 truth First = true;
1568 for (int c1 = 1; c1 < protocontainer<character>::GetSize(); ++c1) {
1569 BusyAnimation();
1570 const character::prototype *Proto = protocontainer<character>::GetProto(c1);
1571 const character::database*const *ConfigData = Proto->GetConfigData();
1572 int ConfigSize = Proto->GetConfigSize();
1573 for (int c2 = 0; c2 < ConfigSize; ++c2) {
1574 if (!ConfigData[c2]->IsAbstract) {
1575 int Config = ConfigData[c2]->Config;
1576 if (First) {
1577 NextDangerIDType = c1;
1578 NextDangerIDConfigIndex = c2;
1579 First = false;
1581 character *Char = Proto->Spawn(Config, NO_EQUIPMENT|NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1582 double NakedDanger = Char->GetRelativeDanger(Player, true);
1583 delete Char;
1584 Char = Proto->Spawn(Config, NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1585 double EquippedDanger = Char->GetRelativeDanger(Player, true);
1586 delete Char;
1587 DangerMap[configid(c1, Config)] = dangerid(NakedDanger, EquippedDanger);
1594 void game::CalculateNextDanger () {
1595 if (IsInWilderness() || !*CurrentLevel->GetLevelScript()->GenerateMonsters()) return;
1596 const character::prototype *Proto = protocontainer<character>::GetProto(NextDangerIDType);
1597 const character::database*const *ConfigData = Proto->GetConfigData();
1598 const character::database *DataBase = ConfigData[NextDangerIDConfigIndex];
1599 dangermap::iterator DangerIterator = DangerMap.find(configid(NextDangerIDType, DataBase->Config));
1600 team *Team = GetTeam(PLAYER_TEAM);
1601 if (DataBase && DangerIterator != DangerMap.end()) {
1602 character *Char = Proto->Spawn(DataBase->Config, NO_EQUIPMENT|NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1603 std::list<character*>::const_iterator i;
1604 double DangerSum = Player->GetRelativeDanger(Char, true);
1605 for (i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i)
1606 if ((*i)->IsEnabled() && !(*i)->IsTemporary() && !RAND_N(10))
1607 DangerSum += (*i)->GetRelativeDanger(Char, true)/4;
1608 double CurrentDanger = 1/DangerSum;
1609 double NakedDanger = DangerIterator->second.NakedDanger;
1610 delete Char;
1611 if (NakedDanger > CurrentDanger) DangerIterator->second.NakedDanger = (NakedDanger*9+CurrentDanger)/10;
1612 Char = Proto->Spawn(DataBase->Config, NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
1613 DangerSum = Player->GetRelativeDanger(Char, true);
1614 for (i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i)
1615 if ((*i)->IsEnabled() && !(*i)->IsTemporary() && !RAND_N(10))
1616 DangerSum += (*i)->GetRelativeDanger(Char, true) / 4;
1617 CurrentDanger = 1/DangerSum;
1618 double EquippedDanger = DangerIterator->second.EquippedDanger;
1619 delete Char;
1620 if (EquippedDanger > CurrentDanger) DangerIterator->second.EquippedDanger = (EquippedDanger*9+CurrentDanger)/10;
1621 if (++NextDangerIDConfigIndex < Proto->GetConfigSize()) return;
1622 for (;;) {
1623 if (++NextDangerIDType >= protocontainer<character>::GetSize()) NextDangerIDType = 1;
1624 Proto = protocontainer<character>::GetProto(NextDangerIDType);
1625 ConfigData = Proto->GetConfigData();
1626 int ConfigSize = Proto->GetConfigSize();
1627 for (int c = 0; c < ConfigSize; ++c) {
1628 if (!ConfigData[c]->IsAbstract) {
1629 NextDangerIDConfigIndex = c;
1630 return;
1634 } else ABORT("It is dangerous to go ice fishing in the summer.");
1638 truth game::TryTravel (int Dungeon, int Area, int EntryIndex, truth AllowHostiles, truth AlliesFollow) {
1639 charactervector Group;
1640 if (LeaveArea(Group, AllowHostiles, AlliesFollow)) {
1641 CurrentDungeonIndex = Dungeon;
1642 EnterArea(Group, Area, EntryIndex);
1643 return true;
1645 return false;
1649 truth game::LeaveArea (charactervector &Group, truth AllowHostiles, truth AlliesFollow) {
1650 if (!IsInWilderness()) {
1651 if (AlliesFollow && !GetCurrentLevel()->CollectCreatures(Group, Player, AllowHostiles)) return false;
1652 Player->Remove();
1653 GetCurrentDungeon()->SaveLevel(SaveName(), CurrentLevelIndex);
1654 } else {
1655 Player->Remove();
1656 GetWorldMap()->GetPlayerGroup().swap(Group);
1657 SaveWorldMap();
1659 return true;
1663 /* Used always when the player enters an area. */
1664 void game::EnterArea (charactervector &Group, int Area, int EntryIndex) {
1665 if (Area != WORLD_MAP) {
1666 Generating = true;
1667 SetIsInWilderness(false);
1668 CurrentLevelIndex = Area;
1669 truth New = !PrepareRandomBone(Area) && !GetCurrentDungeon()->PrepareLevel(Area);
1670 igraph::CreateBackGround(*CurrentLevel->GetLevelScript()->GetBackGroundType());
1671 GetCurrentArea()->SendNewDrawRequest();
1672 v2 Pos = GetCurrentLevel()->GetEntryPos(Player, EntryIndex);
1673 if (Player) {
1674 GetCurrentLevel()->GetLSquare(Pos)->KickAnyoneStandingHereAway();
1675 Player->PutToOrNear(Pos);
1676 } else SetPlayer(GetCurrentLevel()->GetLSquare(Pos)->GetCharacter());
1677 uInt c;
1678 for (c = 0; c < Group.size(); ++c) {
1679 v2 NPCPos = GetCurrentLevel()->GetNearestFreeSquare(Group[c], Pos);
1680 if (NPCPos == ERROR_V2) NPCPos = GetCurrentLevel()->GetRandomSquare(Group[c]);
1681 Group[c]->PutTo(NPCPos);
1683 GetCurrentLevel()->FiatLux();
1684 ctruth *AutoReveal = GetCurrentLevel()->GetLevelScript()->AutoReveal();
1685 if (New && AutoReveal && *AutoReveal) GetCurrentLevel()->Reveal();
1686 ShowLevelMessage();
1687 SendLOSUpdateRequest();
1688 UpdateCamera();
1690 /* Gum solution! */
1691 if (New && CurrentDungeonIndex == ATTNAM && Area == 0) {
1692 GlobalRainLiquid = powder::Spawn(SNOW);
1693 GlobalRainSpeed = v2(-64, 128);
1694 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
1697 if (New && CurrentDungeonIndex == NEW_ATTNAM && Area == 0) {
1698 GlobalRainLiquid = liquid::Spawn(WATER);
1699 GlobalRainSpeed = v2(256, 512);
1700 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
1703 if (New && CurrentDungeonIndex == ELPURI_CAVE && Area == OREE_LAIR) {
1704 GlobalRainLiquid = liquid::Spawn(BLOOD);
1705 GlobalRainSpeed = v2(256, 512);
1706 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
1707 GlobalRainLiquid->SetVolumeNoSignals(200);
1708 CurrentLevel->EnableGlobalRain();
1711 if (New && CurrentDungeonIndex == MUNTUO && Area == 0) {
1712 GlobalRainLiquid = liquid::Spawn(WATER);
1713 GlobalRainSpeed = v2(-64, 1024);
1714 CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
1717 Generating = false;
1718 GetCurrentLevel()->UpdateLOS();
1719 Player->SignalStepFrom(0);
1721 for (c = 0; c < Group.size(); ++c) Group[c]->SignalStepFrom(0);
1723 if (ivanconfig::GetAutoSaveInterval()) Save(GetAutoSaveFileName().CStr());
1724 } else {
1725 igraph::CreateBackGround(GRAY_FRACTAL);
1726 SetIsInWilderness(true);
1727 LoadWorldMap();
1728 SetCurrentArea(WorldMap);
1729 CurrentWSquareMap = WorldMap->GetMap();
1730 GetWorldMap()->GetPlayerGroup().swap(Group);
1731 Player->PutTo(GetWorldMap()->GetEntryPos(Player, EntryIndex));
1732 SendLOSUpdateRequest();
1733 UpdateCamera();
1734 GetWorldMap()->UpdateLOS();
1735 if (ivanconfig::GetAutoSaveInterval()) Save(GetAutoSaveFileName().CStr());
1740 int game::CompareLightToInt (col24 L, col24 Int) {
1741 if ((L & 0xFF0000) > Int || (L & 0xFF00) > Int || (L & 0xFF) > Int) return 1;
1742 if ((L & 0xFF0000) == Int || (L & 0xFF00) == Int || (L & 0xFF) == Int) return 0;
1743 return -1;
1747 void game::SetStandardListAttributes (felist &List) {
1748 List.SetPos(v2(26, 42));
1749 List.SetWidth(652);
1750 List.SetFlags(DRAW_BACKGROUND_AFTERWARDS);
1751 List.SetUpKey(GetMoveCommandKey(KEY_UP_INDEX));
1752 List.SetDownKey(GetMoveCommandKey(KEY_DOWN_INDEX));
1756 void game::InitPlayerAttributeAverage () {
1757 AveragePlayerArmStrengthExperience
1758 = AveragePlayerLegStrengthExperience
1759 = AveragePlayerDexterityExperience
1760 = AveragePlayerAgilityExperience
1761 = 0;
1763 if (!Player->IsHumanoid()) return;
1765 humanoid *Player = static_cast<humanoid*>(GetPlayer());
1766 int Arms = 0;
1767 int Legs = 0;
1768 arm *RightArm = Player->GetRightArm();
1770 if (RightArm && !RightArm->UseMaterialAttributes()) {
1771 AveragePlayerArmStrengthExperience += RightArm->GetStrengthExperience();
1772 AveragePlayerDexterityExperience += RightArm->GetDexterityExperience();
1773 ++Arms;
1776 arm *LeftArm = Player->GetLeftArm();
1778 if (LeftArm && !LeftArm->UseMaterialAttributes()) {
1779 AveragePlayerArmStrengthExperience += LeftArm->GetStrengthExperience();
1780 AveragePlayerDexterityExperience += LeftArm->GetDexterityExperience();
1781 ++Arms;
1784 leg *RightLeg = Player->GetRightLeg();
1786 if (RightLeg && !RightLeg->UseMaterialAttributes()) {
1787 AveragePlayerLegStrengthExperience += RightLeg->GetStrengthExperience();
1788 AveragePlayerAgilityExperience += RightLeg->GetAgilityExperience();
1789 ++Legs;
1792 leg *LeftLeg = Player->GetLeftLeg();
1794 if (LeftLeg && !LeftLeg->UseMaterialAttributes()) {
1795 AveragePlayerLegStrengthExperience += LeftLeg->GetStrengthExperience();
1796 AveragePlayerAgilityExperience += LeftLeg->GetAgilityExperience();
1797 ++Legs;
1800 if (Arms) {
1801 AveragePlayerArmStrengthExperience /= Arms;
1802 AveragePlayerDexterityExperience /= Arms;
1805 if (Legs) {
1806 AveragePlayerLegStrengthExperience /= Legs;
1807 AveragePlayerAgilityExperience /= Legs;
1812 void game::UpdatePlayerAttributeAverage () {
1813 if (!Player->IsHumanoid()) return;
1815 humanoid *Player = static_cast<humanoid*>(GetPlayer());
1816 double PlayerArmStrengthExperience = 0;
1817 double PlayerLegStrengthExperience = 0;
1818 double PlayerDexterityExperience = 0;
1819 double PlayerAgilityExperience = 0;
1820 int Arms = 0;
1821 int Legs = 0;
1822 arm *RightArm = Player->GetRightArm();
1824 if (RightArm && !RightArm->UseMaterialAttributes()) {
1825 PlayerArmStrengthExperience += RightArm->GetStrengthExperience();
1826 PlayerDexterityExperience += RightArm->GetDexterityExperience();
1827 ++Arms;
1830 arm *LeftArm = Player->GetLeftArm();
1832 if (LeftArm && !LeftArm->UseMaterialAttributes()) {
1833 PlayerArmStrengthExperience += LeftArm->GetStrengthExperience();
1834 PlayerDexterityExperience += LeftArm->GetDexterityExperience();
1835 ++Arms;
1838 leg *RightLeg = Player->GetRightLeg();
1840 if (RightLeg && !RightLeg->UseMaterialAttributes()) {
1841 PlayerLegStrengthExperience += RightLeg->GetStrengthExperience();
1842 PlayerAgilityExperience += RightLeg->GetAgilityExperience();
1843 ++Legs;
1846 leg *LeftLeg = Player->GetLeftLeg();
1848 if (LeftLeg && !LeftLeg->UseMaterialAttributes()) {
1849 PlayerLegStrengthExperience += LeftLeg->GetStrengthExperience();
1850 PlayerAgilityExperience += LeftLeg->GetAgilityExperience();
1851 ++Legs;
1854 if (Arms) {
1855 AveragePlayerArmStrengthExperience = (49 * AveragePlayerArmStrengthExperience + PlayerArmStrengthExperience / Arms) / 50;
1856 AveragePlayerDexterityExperience = (49 * AveragePlayerDexterityExperience + PlayerDexterityExperience / Arms) / 50;
1859 if (Legs) {
1860 AveragePlayerLegStrengthExperience = (49 * AveragePlayerLegStrengthExperience + PlayerLegStrengthExperience / Legs) / 50;
1861 AveragePlayerAgilityExperience = (49 * AveragePlayerAgilityExperience + PlayerAgilityExperience / Legs) / 50;
1866 void game::CallForAttention (v2 Pos, int RangeSquare) {
1867 for (int c = 0; c < GetTeams(); ++c) {
1868 if (GetTeam(c)->HasEnemy())
1869 for (std::list<character*>::const_iterator i = GetTeam(c)->GetMember().begin(); i != GetTeam(c)->GetMember().end(); ++i)
1870 if ((*i)->IsEnabled()) {
1871 sLong ThisDistance = HypotSquare(sLong((*i)->GetPos().X) - Pos.X, sLong((*i)->GetPos().Y) - Pos.Y);
1872 if (ThisDistance <= RangeSquare && !(*i)->IsGoingSomeWhere()) (*i)->SetGoingTo(Pos);
1878 outputfile &operator << (outputfile &SaveFile, const homedata *HomeData) {
1879 if (HomeData) {
1880 SaveFile.Put(1);
1881 SaveFile << HomeData->Pos << HomeData->Dungeon << HomeData->Level << HomeData->Room;
1882 } else SaveFile.Put(0);
1883 return SaveFile;
1887 inputfile &operator >> (inputfile &SaveFile, homedata *&HomeData) {
1888 if (SaveFile.Get()) {
1889 HomeData = new homedata;
1890 SaveFile >> HomeData->Pos >> HomeData->Dungeon >> HomeData->Level >> HomeData->Room;
1892 return SaveFile;
1896 feuLong game::CreateNewCharacterID (character *NewChar) {
1897 feuLong ID = NextCharacterID++;
1898 /*k8:??? if(CharacterIDMap.find(ID) != CharacterIDMap.end())
1899 int esko = esko = 2;*/
1900 CharacterIDMap.insert(std::make_pair(ID, NewChar));
1901 return ID;
1905 feuLong game::CreateNewItemID (item *NewItem) {
1906 feuLong ID = NextItemID++;
1907 /*k8:??? if(ItemIDMap.find(ID) != ItemIDMap.end())
1908 int esko = esko = 2;*/
1909 if (NewItem) ItemIDMap.insert(std::make_pair(ID, NewItem));
1910 return ID;
1914 feuLong game::CreateNewTrapID (entity *NewTrap) {
1915 feuLong ID = NextTrapID++;
1916 /*k8:??? if(TrapIDMap.find(ID) != TrapIDMap.end())
1917 int esko = esko = 2;*/
1918 if (NewTrap) TrapIDMap.insert(std::make_pair(ID, NewTrap));
1919 return ID;
1923 character *game::SearchCharacter (feuLong ID) {
1924 characteridmap::iterator Iterator = CharacterIDMap.find(ID);
1925 return Iterator != CharacterIDMap.end() ? Iterator->second : 0;
1929 item *game::SearchItem (feuLong ID) {
1930 itemidmap::iterator Iterator = ItemIDMap.find(ID);
1931 return Iterator != ItemIDMap.end() ? Iterator->second : 0;
1935 entity *game::SearchTrap (feuLong ID) {
1936 trapidmap::iterator Iterator = TrapIDMap.find(ID);
1937 return Iterator != TrapIDMap.end() ? Iterator->second : 0;
1941 outputfile &operator << (outputfile &SaveFile, const configid &Value) {
1942 SaveFile.Write(reinterpret_cast<cchar*>(&Value), sizeof(Value));
1943 return SaveFile;
1947 inputfile &operator >> (inputfile &SaveFile, configid &Value) {
1948 SaveFile.Read(reinterpret_cast<char*>(&Value), sizeof(Value));
1949 return SaveFile;
1953 outputfile &operator << (outputfile &SaveFile, const dangerid &Value) {
1954 SaveFile << Value.NakedDanger << Value.EquippedDanger;
1955 return SaveFile;
1959 inputfile &operator >> (inputfile &SaveFile, dangerid &Value) {
1960 SaveFile >> Value.NakedDanger >> Value.EquippedDanger;
1961 return SaveFile;
1965 /* The program can only create directories to the deepness of one, no more... */
1966 festring game::GetHomeDir () {
1967 festring Dir;
1968 Dir << getenv("HOME") << '/';
1969 return Dir;
1973 festring game::GetSaveDir () {
1974 festring Dir;
1975 #ifdef LOCAL_SAVES
1976 Dir << ivanconfig::GetMyDir() << "/Save/";
1977 #else
1978 Dir << getenv("HOME") << "/IvanSave/";
1979 #endif
1980 return Dir;
1984 festring game::GetGameDir () {
1985 /*k8! return DATADIR "/ivan/"; */
1986 /*k8! return DATADIR "/"; */
1987 festring Dir;
1988 Dir << ivanconfig::GetMyDir() << "/";
1989 return Dir;
1993 festring game::GetBoneDir () {
1994 /*k8! return LOCAL_STATE_DIR "/Bones/";*/
1995 festring Dir;
1996 #ifdef LOCAL_SAVES
1997 Dir << ivanconfig::GetMyDir() << "/Save/Bones/";
1998 #else
1999 Dir << getenv("HOME") << "/IvanSave/Bones/";
2000 #endif
2001 return Dir;
2005 level *game::GetLevel (int I) {
2006 return GetCurrentDungeon()->GetLevel(I);
2010 int game::GetLevels () {
2011 return GetCurrentDungeon()->GetLevels();
2015 void game::SignalDeath (ccharacter *Ghost, ccharacter *Murderer, festring DeathMsg) {
2016 if (InWilderness) DeathMsg << " in the world map";
2017 else DeathMsg << " in " << GetCurrentDungeon()->GetLevelDescription(CurrentLevelIndex);
2018 massacremap *MassacreMap;
2019 if (!Murderer) {
2020 ++MiscMassacreAmount;
2021 MassacreMap = &MiscMassacreMap;
2022 } else if(Murderer->IsPlayer()) {
2023 ++PlayerMassacreAmount;
2024 MassacreMap = &PlayerMassacreMap;
2025 } else if(Murderer->IsPet()) {
2026 ++PetMassacreAmount;
2027 MassacreMap = &PetMassacreMap;
2028 } else {
2029 ++MiscMassacreAmount;
2030 MassacreMap = &MiscMassacreMap;
2033 massacreid MI(Ghost->GetType(), Ghost->GetConfig(), Ghost->GetAssignedName());
2034 massacremap::iterator i = MassacreMap->find(MI);
2036 if (i == MassacreMap->end()) {
2037 i = MassacreMap->insert(std::make_pair(MI, killdata(1, Ghost->GetGenerationDanger()))).first;
2038 i->second.Reason.push_back(killreason(DeathMsg, 1));
2039 } else {
2040 ++i->second.Amount;
2041 i->second.DangerSum += Ghost->GetGenerationDanger();
2042 std::vector<killreason>& Reason = i->second.Reason;
2043 uInt c;
2044 for (c = 0; c < Reason.size(); ++c) {
2045 if (Reason[c].String == DeathMsg) {
2046 ++Reason[c].Amount;
2047 break;
2050 if (c == Reason.size()) Reason.push_back(killreason(DeathMsg, 1));
2055 void game::DisplayMassacreLists () {
2056 DisplayMassacreList(PlayerMassacreMap, "directly by you.", PlayerMassacreAmount);
2057 DisplayMassacreList(PetMassacreMap, "by your allies.", PetMassacreAmount);
2058 DisplayMassacreList(MiscMassacreMap, "by some other reason.", MiscMassacreAmount);
2062 struct massacresetentry {
2063 bool operator < (const massacresetentry &MSE) const { return festring::IgnoreCaseCompare(Key, MSE.Key); }
2064 festring Key;
2065 festring String;
2066 std::vector<festring> Details;
2067 int ImageKey;
2071 void game::DisplayMassacreList (const massacremap &MassacreMap, cchar *Reason, sLong Amount) {
2072 std::set<massacresetentry> MassacreSet;
2073 festring FirstPronoun;
2074 truth First = true;
2075 charactervector GraveYard;
2077 for (massacremap::const_iterator i1 = MassacreMap.begin(); i1 != MassacreMap.end(); ++i1) {
2078 character *Victim = protocontainer<character>::GetProto(i1->first.Type)->Spawn(i1->first.Config);
2079 Victim->SetAssignedName(i1->first.Name);
2080 massacresetentry Entry;
2081 GraveYard.push_back(Victim);
2082 Entry.ImageKey = AddToCharacterDrawVector(Victim);
2083 if (i1->second.Amount == 1) {
2084 Victim->AddName(Entry.Key, UNARTICLED);
2085 Victim->AddName(Entry.String, INDEFINITE);
2086 } else {
2087 Victim->AddName(Entry.Key, PLURAL);
2088 Entry.String << i1->second.Amount << ' ' << Entry.Key;
2090 if (First) {
2091 FirstPronoun = Victim->GetSex() == UNDEFINED ? "it" : Victim->GetSex() == MALE ? "he" : "she";
2092 First = false;
2094 const std::vector<killreason>& Reason = i1->second.Reason;
2095 std::vector<festring>& Details = Entry.Details;
2096 if (Reason.size() == 1) {
2097 festring Begin;
2098 if (Reason[0].Amount == 1) Begin = "";
2099 else if(Reason[0].Amount == 2) Begin = "both ";
2100 else Begin = "all ";
2101 Details.push_back(Begin + Reason[0].String);
2102 } else {
2103 for (uInt c = 0; c < Reason.size(); ++c) Details.push_back(CONST_S("")+Reason[c].Amount+' '+Reason[c].String);
2104 std::sort(Details.begin(), Details.end(), ignorecaseorderer());
2106 MassacreSet.insert(Entry);
2108 sLong Total = PlayerMassacreAmount+PetMassacreAmount+MiscMassacreAmount;
2109 festring MainTopic;
2110 if (Total == 1) MainTopic << "One creature perished during your adventure.";
2111 else MainTopic << Total << " creatures perished during your adventure.";
2112 felist List(MainTopic);
2113 SetStandardListAttributes(List);
2114 List.SetPageLength(15);
2115 List.AddFlags(SELECTABLE);
2116 List.SetEntryDrawer(CharacterEntryDrawer);
2117 List.AddDescription(CONST_S(""));
2118 festring SideTopic;
2119 if (Amount != Total) {
2120 SideTopic = CONST_S("The following ");
2121 if (Amount == 1) SideTopic << "one was killed " << Reason;
2122 else SideTopic << Amount << " were killed " << Reason;
2123 } else {
2124 if (Amount == 1) {
2125 FirstPronoun.Capitalize();
2126 SideTopic << FirstPronoun << " was killed " << Reason;
2127 } else SideTopic << "They were all killed " << Reason;
2129 List.AddDescription(SideTopic);
2130 List.AddDescription(CONST_S(""));
2131 List.AddDescription("Choose a type of creatures to browse death details.");
2132 std::set<massacresetentry>::const_iterator i2;
2133 for (i2 = MassacreSet.begin(); i2 != MassacreSet.end(); ++i2) List.AddEntry(i2->String, LIGHT_GRAY, 0, i2->ImageKey);
2134 for (;;) {
2135 int Chosen = List.Draw();
2136 if (Chosen & FELIST_ERROR_BIT) break;
2137 felist SubList(CONST_S("Massacre details"));
2138 SetStandardListAttributes(SubList);
2139 SubList.SetPageLength(20);
2140 int Counter = 0;
2141 for (i2 = MassacreSet.begin(); i2 != MassacreSet.end(); ++i2, ++Counter) {
2142 if (Counter == Chosen) {
2143 for (uInt c = 0; c < i2->Details.size(); ++c) SubList.AddEntry(i2->Details[c], LIGHT_GRAY);
2144 break;
2147 SubList.Draw();
2149 ClearCharacterDrawVector();
2150 for (uInt c = 0; c < GraveYard.size(); ++c) delete GraveYard[c];
2154 truth game::MassacreListsEmpty () {
2155 return PlayerMassacreMap.empty() && PetMassacreMap.empty() && MiscMassacreMap.empty();
2159 #ifdef WIZARD
2160 void game::SeeWholeMap () {
2161 if (SeeWholeMapCheatMode < 2) ++SeeWholeMapCheatMode; else SeeWholeMapCheatMode = 0;
2162 GetCurrentArea()->SendNewDrawRequest();
2164 #endif
2167 void game::CreateBone () {
2168 if (!WizardModeIsActive() && !IsInWilderness() && RAND() & 3 && GetCurrentLevel()->PreProcessForBone()) {
2169 int BoneIndex;
2170 festring BoneName;
2171 for (BoneIndex = 0; BoneIndex < 1000; ++BoneIndex) {
2172 BoneName = GetBoneDir()+"bon"+CurrentDungeonIndex+CurrentLevelIndex+BoneIndex;
2173 if (!inputfile::fileExists(BoneName)) break;
2175 if (BoneIndex != 1000) {
2176 //festring BoneName = GetBoneDir()+"bon"+CurrentDungeonIndex+CurrentLevelIndex+BoneIndex;
2177 fprintf(stderr, "creating bone file: [%s]\n", BoneName.CStr());
2178 outputfile BoneFile(BoneName);
2179 BoneFile << int(BONE_FILE_VERSION) << PlayerName << CurrentLevel;
2185 truth game::PrepareRandomBone (int LevelIndex) {
2186 if (/*k8:WizardModeIsActive() ||*/ GetCurrentDungeon()->IsGenerated(LevelIndex) || !*GetCurrentDungeon()->GetLevelScript(LevelIndex)->CanGenerateBone()) return false;
2187 int BoneIndex;
2188 festring BoneName;
2189 for (BoneIndex = 0; BoneIndex < 1000; ++BoneIndex) {
2190 BoneName = GetBoneDir()+"bon"+CurrentDungeonIndex+LevelIndex+BoneIndex;
2191 inputfile BoneFile(BoneName, 0, false);
2192 if (BoneFile.IsOpen() && !(RAND() & 7)) {
2193 if (ReadType<int>(BoneFile) != BONE_FILE_VERSION) {
2194 BoneFile.Close();
2195 remove(BoneName.CStr());
2196 continue;
2198 festring Name;
2199 BoneFile >> Name;
2200 level *NewLevel = GetCurrentDungeon()->LoadLevel(BoneFile, LevelIndex);
2201 if (!NewLevel->PostProcessForBone()) {
2202 delete NewLevel;
2203 GetBoneItemIDMap().clear();
2204 GetBoneCharacterIDMap().clear();
2205 continue;
2207 NewLevel->FinalProcessForBone();
2208 GetBoneItemIDMap().clear();
2209 GetBoneCharacterIDMap().clear();
2210 SetCurrentArea(NewLevel);
2211 CurrentLevel = NewLevel;
2212 CurrentLSquareMap = NewLevel->GetMap();
2213 GetCurrentDungeon()->SetIsGenerated(LevelIndex, true);
2214 if (Name == PlayerName) ADD_MESSAGE("This place is oddly familiar. Like you had been here in one of your past lives.");
2215 else ADD_MESSAGE("You smell the stench of death.");
2216 break;
2219 Generating = true;
2220 if (BoneIndex != 1000) {
2221 remove(BoneName.CStr());
2222 return true;
2224 return false;
2228 double game::CalculateAverageDanger (const charactervector &EnemyVector, character *Char) {
2229 double DangerSum = 0;
2230 int Enemies = 0;
2231 for (uInt c = 0; c < EnemyVector.size(); ++c) {
2232 DangerSum += EnemyVector[c]->GetRelativeDanger(Char, true);
2233 ++Enemies;
2235 return DangerSum/Enemies;
2239 double game::CalculateAverageDangerOfAllNormalEnemies () {
2240 double DangerSum = 0;
2241 int Enemies = 0;
2242 for (int c1 = 1; c1 < protocontainer<character>::GetSize(); ++c1) {
2243 const character::prototype *Proto = protocontainer<character>::GetProto(c1);
2244 const character::database*const *ConfigData = Proto->GetConfigData();
2245 int ConfigSize = Proto->GetConfigSize();
2246 for (int c2 = 0; c2 < ConfigSize; ++c2) {
2247 if (!ConfigData[c2]->IsAbstract && !ConfigData[c2]->IsUnique && ConfigData[c2]->CanBeGenerated) {
2248 DangerSum += DangerMap.find(configid(c1, ConfigData[c2]->Config))->second.EquippedDanger;
2249 ++Enemies;
2253 return DangerSum/Enemies;
2257 character *game::CreateGhost () {
2258 double AverageDanger = CalculateAverageDangerOfAllNormalEnemies();
2259 charactervector EnemyVector;
2260 protosystem::CreateEveryNormalEnemy(EnemyVector);
2261 ghost *Ghost = ghost::Spawn();
2262 Ghost->SetTeam(GetTeam(MONSTER_TEAM));
2263 Ghost->SetGenerationDanger(CurrentLevel->GetDifficulty());
2264 Ghost->SetOwnerSoul(PlayerName);
2265 Ghost->SetIsActive(false);
2266 Ghost->EditAllAttributes(-4);
2267 Player->SetSoulID(Ghost->GetID());
2268 while (CalculateAverageDanger(EnemyVector, Ghost) > AverageDanger && Ghost->EditAllAttributes(1));
2269 for (uInt c = 0; c < EnemyVector.size(); ++c) delete EnemyVector[c];
2270 return Ghost;
2274 int game::GetMoveCommandKey (int I) {
2275 if (!ivanconfig::GetUseAlternativeKeys()) return MoveNormalCommandKey[I];
2276 return MoveAbnormalCommandKey[I];
2280 sLong game::GetScore () {
2281 double Counter = 0;
2282 massacremap::const_iterator i;
2283 massacremap SumMap = PlayerMassacreMap;
2284 for (i = PetMassacreMap.begin(); i != PetMassacreMap.end(); ++i) {
2285 killdata &KillData = SumMap[i->first];
2286 KillData.Amount += i->second.Amount;
2287 KillData.DangerSum += i->second.DangerSum;
2289 for (i = SumMap.begin(); i != SumMap.end(); ++i) {
2290 character *Char = protocontainer<character>::GetProto(i->first.Type)->Spawn(i->first.Config);
2291 int SumOfAttributes = Char->GetSumOfAttributes();
2292 Counter += sqrt(i->second.DangerSum / DEFAULT_GENERATION_DANGER) * SumOfAttributes * SumOfAttributes;
2293 delete Char;
2295 return sLong(0.01*Counter);
2299 /* Only works if New Attnam is loaded */
2300 truth game::TweraifIsFree () {
2301 for (std::list<character*>::const_iterator i = GetTeam(COLONIST_TEAM)->GetMember().begin(); i != GetTeam(COLONIST_TEAM)->GetMember().end(); ++i)
2302 if ((*i)->IsEnabled()) return false;
2303 return true;
2307 // returns true if date is christmaseve or day
2308 truth game::IsXMas () {
2309 time_t Time = time(0);
2310 struct tm *TM = localtime(&Time);
2311 return (TM->tm_mon == 11 && (TM->tm_mday == 24 || TM->tm_mday == 25));
2315 int game::AddToItemDrawVector (const itemvector &What) {
2316 ItemDrawVector.push_back(What);
2317 return ItemDrawVector.size()-1;
2321 v2 ItemDisplacement[3][3] = {
2322 { v2(0, 0), ERROR_V2, ERROR_V2 },
2323 { v2(-2, -2), v2(2, 2), ERROR_V2 },
2324 { v2(-4, -4), v2(0, 0), v2(4, 4) }
2328 void game::ItemEntryDrawer (bitmap *Bitmap, v2 Pos, uInt I) {
2329 blitdata B = {
2330 Bitmap,
2331 { 0, 0 },
2332 { 0, 0 },
2333 { TILE_SIZE, TILE_SIZE },
2334 { NORMAL_LUMINANCE },
2335 TRANSPARENT_COLOR,
2336 ALLOW_ANIMATE
2338 itemvector ItemVector = ItemDrawVector[I];
2339 int Amount = Min<int>(ItemVector.size(), 3);
2340 for (int c = 0; c < Amount; ++c) {
2341 v2 Displacement = ItemDisplacement[Amount-1][c];
2342 if (!ItemVector[0]->HasNormalPictureDirection()) Displacement.X = -Displacement.X;
2343 B.Dest = Pos+Displacement;
2344 if (ItemVector[c]->AllowAlphaEverywhere()) B.CustomData |= ALLOW_ALPHA;
2345 ItemVector[c]->Draw(B);
2346 B.CustomData &= ~ALLOW_ALPHA;
2348 if (ItemVector.size() > 3) {
2349 B.Src.X = 0;
2350 B.Src.Y = 16;
2351 B.Dest = ItemVector[0]->HasNormalPictureDirection() ? Pos+v2(11, -2) : Pos+v2(-2, -2);
2352 B.Flags = 0;
2353 igraph::GetSymbolGraphic()->NormalMaskedBlit(B);
2358 int game::AddToCharacterDrawVector (character *What) {
2359 CharacterDrawVector.push_back(What);
2360 return CharacterDrawVector.size()-1;
2364 void game::CharacterEntryDrawer (bitmap *Bitmap, v2 Pos, uInt I) {
2365 if (CharacterDrawVector[I]) {
2366 blitdata B = {
2367 Bitmap,
2368 { 0, 0 },
2369 { Pos.X, Pos.Y },
2370 { TILE_SIZE, TILE_SIZE },
2371 { NORMAL_LUMINANCE },
2372 TRANSPARENT_COLOR,
2373 ALLOW_ANIMATE|ALLOW_ALPHA
2375 CharacterDrawVector[I]->DrawBodyParts(B);
2380 void game::GodEntryDrawer (bitmap *Bitmap, v2 Pos, uInt I) {
2381 blitdata B = {
2382 Bitmap,
2383 { I << 4, 0 },
2384 { Pos.X, Pos.Y },
2385 { TILE_SIZE, TILE_SIZE },
2386 { 0 },
2387 TRANSPARENT_COLOR,
2390 igraph::GetSymbolGraphic()->NormalMaskedBlit(B);
2394 character *game::GetSumo () {
2395 return GetCurrentLevel()->GetLSquare(SUMO_ROOM_POS)->GetRoom()->GetMaster();
2399 truth game::TryToEnterSumoArena () {
2400 character *Sumo = GetSumo();
2401 if (!Sumo || !Sumo->IsEnabled() || Sumo->GetRelation(Player) == HOSTILE || !Player->CanBeSeenBy(Sumo)) return true;
2402 if (TweraifIsFree()) {
2403 ADD_MESSAGE("\"You started this stupid revolution, after which I've been constantly hungry. Get lost!\"");
2404 return false;
2406 if (PlayerIsSumoChampion()) {
2407 ADD_MESSAGE("\"I don't really enjoy losing, especially many times to the same guy. Go away.\"");
2408 return false;
2410 if (Player->IsPolymorphed()) {
2411 ADD_MESSAGE("\"Don't try to cheat. Come back when you're normal again.\"");
2412 return false;
2414 if (Player->GetHungerState() < SATIATED) {
2415 ADD_MESSAGE("\"Your figure is too slender for this sport. Eat a lot more and come back.\"");
2416 return false;
2418 if (Player->GetHungerState() < BLOATED) {
2419 ADD_MESSAGE("\"You're still somewhat too thin. Eat some more and we'll compete.\"");
2420 return false;
2422 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.\"");
2423 if (!TruthQuestion("Do you want to challenge him? [y/N]")) return false;
2424 SumoWrestling = true;
2425 character *MirrorPlayer = Player->Duplicate(IGNORE_PROHIBITIONS);
2426 character *MirrorSumo = Sumo->Duplicate(IGNORE_PROHIBITIONS);
2427 SetPlayer(MirrorPlayer);
2428 charactervector Spectators;
2429 if (Player->GetTeam()->GetRelation(GetTeam(TOURIST_GUIDE_TEAM)) != HOSTILE &&
2430 Player->GetTeam()->GetRelation(GetTeam(TOURIST_TEAM)) != HOSTILE) {
2431 GetTeam(TOURIST_GUIDE_TEAM)->MoveMembersTo(Spectators);
2432 GetTeam(TOURIST_TEAM)->MoveMembersTo(Spectators);
2434 GetCurrentDungeon()->SaveLevel(SaveName(), 0);
2435 charactervector test;
2436 EnterArea(test, 1, STAIRS_UP);
2437 MirrorSumo->PutTo(SUMO_ARENA_POS+v2(6, 5));
2438 MirrorSumo->ChangeTeam(GetTeam(SUMO_TEAM));
2439 GetCurrentLevel()->GetLSquare(SUMO_ARENA_POS)->GetRoom()->SetMasterID(MirrorSumo->GetID());
2440 for (uInt c = 0; c < Spectators.size(); ++c) Spectators[c]->PutToOrNear(SUMO_ARENA_POS + v2(6, 10));
2441 throw areachangerequest();
2442 return true;
2446 truth game::TryToExitSumoArena () {
2447 if (GetTeam(PLAYER_TEAM)->GetRelation(GetTeam(NEW_ATTNAM_TEAM)) == HOSTILE) return true;
2448 itemvector IVector;
2449 charactervector CVector;
2450 if (IsSumoWrestling()) {
2451 if (TruthQuestion("Do you really wish to give up? [y/N]")) return EndSumoWrestling(LOST);
2452 return false;
2453 } else {
2454 Player->Remove();
2455 GetCurrentLevel()->CollectEverything(IVector, CVector);
2456 GetCurrentDungeon()->SaveLevel(SaveName(), 1);
2457 std::vector<character*> test;
2458 EnterArea(test, 0, STAIRS_DOWN);
2459 Player->GetStackUnder()->AddItems(IVector);
2460 if (!IVector.empty()) {
2461 character *Sumo = GetSumo();
2462 if (Sumo && Sumo->GetRelation(Player) != HOSTILE && Player->CanBeSeenBy(Sumo)) ADD_MESSAGE("\"Don't leave anything there, please.\"");
2464 v2 PlayerPos = Player->GetPos();
2465 for (uInt c = 0; c < CVector.size(); ++c) CVector[c]->PutNear(PlayerPos);
2466 throw areachangerequest();
2467 return true;
2472 truth game::EndSumoWrestling (int Result) {
2473 msgsystem::LeaveBigMessageMode();
2474 if (Result == LOST) AskForKeyPress("You lose. [press any key to continue]");
2475 else if (Result == WON) AskForKeyPress("You win! [press any key to continue]");
2476 else if (Result == DISQUALIFIED) AskForKeyPress("You are disqualified! [press any key to continue]");
2477 character *Sumo = GetCurrentLevel()->GetLSquare(SUMO_ARENA_POS)->GetRoom()->GetMaster();
2478 /* We'll make a throw soon so deletes are allowed */
2479 if (Sumo) {
2480 Sumo->Remove();
2481 delete Sumo;
2483 Player->Remove();
2484 delete Player;
2485 SetPlayer(0);
2486 itemvector IVector;
2487 charactervector CVector;
2488 GetCurrentLevel()->CollectEverything(IVector, CVector);
2489 GetCurrentDungeon()->SaveLevel(SaveName(), 1);
2490 charactervector test;
2491 EnterArea(test, 0, STAIRS_DOWN);
2492 SumoWrestling = false;
2493 Player->GetStackUnder()->AddItems(IVector);
2494 v2 PlayerPos = Player->GetPos();
2495 for (uInt c = 0; c < CVector.size(); ++c) CVector[c]->PutNear(PlayerPos);
2496 if (Result == LOST) ADD_MESSAGE("\"I hope you've learned your lesson now!\"");
2497 else if (Result == DISQUALIFIED) ADD_MESSAGE("\"Don't do that again or I'll be really angry!\"");
2498 else {
2499 PlayerSumoChampion = true;
2500 character *Sumo = GetSumo();
2501 festring Msg = Sumo->GetName(DEFINITE)+" seems humbler than before. \"Darn. You bested me.\n";
2502 Msg << "Here's a little something as a reward\", " << Sumo->GetPersonalPronoun() << " says and hands you a belt of levitation.\n\"";
2503 (belt::Spawn(BELT_OF_LEVITATION))->MoveTo(Player->GetStack());
2504 Msg << "Allow me to also teach you a few nasty martial art tricks the years have taught me.\"";
2505 Player->GetCWeaponSkill(UNARMED)->AddHit(100000);
2506 Player->GetCWeaponSkill(KICK)->AddHit(100000);
2507 character *Imperialist = GetCurrentLevel()->GetLSquare(5, 5)->GetRoom()->GetMaster();
2508 if (Imperialist && Imperialist->GetRelation(Player) != HOSTILE) {
2509 v2 Pos = Player->GetPos()+v2(0, 1);
2510 GetCurrentLevel()->GetLSquare(Pos)->KickAnyoneStandingHereAway();
2511 Imperialist->Remove();
2512 Imperialist->PutTo(Pos);
2513 Msg << "\n\nSuddenly you notice " << Imperialist->GetName(DEFINITE) << " has also entered.\n"
2514 "\"I see we have a promising fighter among us. I had already heard of your\n"
2515 "adventures outside the village, but hardly could I believe that one day you\n"
2516 "would defeat even the mighty Huang Ming Pong! A hero such as you is bound\n"
2517 "to become world famous, and can earn a fortune if wealthy sponsors are behind\n"
2518 "him. May I therefore propose a mutually profitable contract: I'll give you this\n"
2519 "nice shirt with my company's ad, and you'll wear it as you journey bravely to\n"
2520 "the unknown and fight epic battles against the limitless minions of evil. I'll\n"
2521 "reward you well when you return, depending on how much you have used it.\"";
2522 Player->GetStack()->AddItem(decosadshirt::Spawn());
2524 TextScreen(Msg);
2525 GetCurrentArea()->SendNewDrawRequest();
2526 DrawEverything();
2528 Player->EditNP(-25000);
2529 Player->CheckStarvationDeath(CONST_S("exhausted after controlling a mirror image for too sLong"));
2530 throw areachangerequest();
2531 return true;
2535 rain *game::ConstructGlobalRain () {
2536 return new rain(GlobalRainLiquid, static_cast<lsquare*>(GetSquareInLoad()), GlobalRainSpeed, MONSTER_TEAM, false);
2540 v2 game::GetSunLightDirectionVector () {
2541 int Index = Tick % 48000 / 1000;
2542 /* Should have the same sign as sin(PI * Index / 24) and XTable[Index] /
2543 YTable[Index] should equal roughly -tan(PI * Index / 24). Also, vector
2544 (XTable[Index], YTable[Index]) + P should not be a valid position of
2545 any possible level L for any P belonging to L. */
2546 static int XTable[48] = {
2547 0, 1000, 1000, 1000, 1000, 1000,
2548 1000, 1303, 1732, 2414, 3732, 7596,
2549 1000, 7596, 3732, 2414, 1732, 1303,
2550 1000, 1000, 1000, 1000, 1000, 1000,
2551 0, -1000, -1000, -1000, -1000, -1000,
2552 -1000, -1303, -1732, -2414, -3732, -7596,
2553 -1000, -7596, -3732, -2414, -1732, -1303,
2554 -1000, -1000, -1000, -1000, -1000, -1000 };
2555 /* Should have the same sign as -cos(PI * Index / 24) */
2556 static int YTable[48] = { -1000, -7596, -3732, -2414, -1732, -1303,
2557 -1000, -1000, -1000, -1000, -1000, -1000,
2558 0, 1000, 1000, 1000, 1000, 1000,
2559 1000, 1303, 1732, 2414, 3732, 7596,
2560 1000, 7596, 3732, 2414, 1732, 1303,
2561 1000, 1000, 1000, 1000, 1000, 1000,
2562 0, -1000, -1000, -1000, -1000, -1000,
2563 -1000, -1303, -1732, -2414, -3732, -7596 };
2564 return v2(XTable[Index], YTable[Index]);
2568 int game::CalculateMinimumEmitationRadius (col24 E) {
2569 int MaxElement = Max(GetRed24(E), GetGreen24(E), GetBlue24(E));
2570 return int(sqrt(double(MaxElement << 7) / LIGHT_BORDER - 120.));
2574 feuLong game::IncreaseSquarePartEmitationTicks () {
2575 if ((SquarePartEmitationTick += 2) == 0x100) {
2576 CurrentLevel->InitSquarePartEmitationTicks();
2577 SquarePartEmitationTick = 2;
2579 return SquarePartEmitationTick;
2583 bool game::Wish (character *Wisher, cchar *MsgSingle, cchar *MsgPair, bool canAbort) {
2584 for (;;) {
2585 festring oldDef = DefaultWish;
2586 festring Temp = DefaultQuestion(CONST_S("What do you want to wish for?"), DefaultWish);
2587 if (DefaultWish == "nothing" && canAbort) {
2588 DefaultWish = oldDef;
2589 return false;
2591 item *TempItem = protosystem::CreateItem(Temp, Wisher->IsPlayer());
2592 if (TempItem) {
2593 Wisher->GetStack()->AddItem(TempItem);
2594 TempItem->SpecialGenerationHandler();
2595 if (TempItem->HandleInPairs()) ADD_MESSAGE(MsgPair, TempItem->CHAR_NAME(PLURAL));
2596 else ADD_MESSAGE(MsgSingle, TempItem->CHAR_NAME(INDEFINITE));
2597 return true;
2603 festring game::DefaultQuestion (festring Topic, festring &Default, stringkeyhandler KeyHandler) {
2604 festring ShortDefault = Default;
2605 if (Default.GetSize() > 29) {
2606 ShortDefault.Resize(27);
2607 ShortDefault = ShortDefault << CONST_S("...");
2609 if (!Default.IsEmpty()) Topic << " [" << ShortDefault << ']';
2610 festring Answer = StringQuestion(Topic, WHITE, 0, 80, false, KeyHandler);
2611 if (Answer.IsEmpty()) Answer = Default;
2612 return Default = Answer;
2616 void game::GetTime (ivantime &Time) {
2617 Time.Hour = 12 + Tick / 2000;
2618 Time.Day = Time.Hour / 24 + 1;
2619 Time.Hour %= 24;
2620 Time.Min = Tick % 2000 * 60 / 2000;
2624 truth NameOrderer (character *C1, character *C2) {
2625 return festring::IgnoreCaseCompare(C1->GetName(UNARTICLED), C2->GetName(UNARTICLED));
2629 truth game::PolymorphControlKeyHandler (int Key, festring &String) {
2630 if (Key == '?') {
2631 felist List(CONST_S("List of known creatures and their intelligence requirements"));
2632 SetStandardListAttributes(List);
2633 List.SetPageLength(15);
2634 List.AddFlags(SELECTABLE);
2635 protosystem::CreateEverySeenCharacter(CharacterDrawVector);
2636 std::sort(CharacterDrawVector.begin(), CharacterDrawVector.end(), NameOrderer);
2637 List.SetEntryDrawer(CharacterEntryDrawer);
2638 std::vector<festring> StringVector;
2639 uInt c;
2640 for (c = 0; c < CharacterDrawVector.size(); ++c) {
2641 character *Char = CharacterDrawVector[c];
2642 if (Char->CanBeWished()) {
2643 festring Entry;
2644 Char->AddName(Entry, UNARTICLED);
2645 StringVector.push_back(Entry);
2646 int Req = Char->GetPolymorphIntelligenceRequirement();
2647 if (Char->IsSameAs(Player) || (Player->GetPolymorphBackup() && Player->GetPolymorphBackup()->IsSameAs(Char))) Req = 0;
2648 Entry << " (" << Req << ')';
2649 int Int = Player->GetAttribute(INTELLIGENCE);
2650 List.AddEntry(Entry, Req > Int ? RED : LIGHT_GRAY, 0, c);
2653 int Chosen = List.Draw();
2654 for (c = 0; c < CharacterDrawVector.size(); ++c) delete CharacterDrawVector[c];
2655 if (!(Chosen & FELIST_ERROR_BIT)) String = StringVector[Chosen];
2656 CharacterDrawVector.clear();
2657 return true;
2659 return false;
2663 outputfile &operator << (outputfile &SaveFile, const killdata &Value) {
2664 SaveFile << Value.Amount << Value.DangerSum << Value.Reason;
2665 return SaveFile;
2669 inputfile &operator >> (inputfile &SaveFile, killdata &Value) {
2670 SaveFile >> Value.Amount >> Value.DangerSum >> Value.Reason;
2671 return SaveFile;
2675 outputfile &operator << (outputfile &SaveFile, const killreason &Value) {
2676 SaveFile << Value.Amount << Value.String;
2677 return SaveFile;
2681 inputfile &operator >> (inputfile &SaveFile, killreason &Value) {
2682 SaveFile >> Value.Amount >> Value.String;
2683 return SaveFile;
2687 truth DistanceOrderer (character *C1, character *C2) {
2688 v2 PlayerPos = PLAYER->GetPos();
2689 v2 Pos1 = C1->GetPos();
2690 v2 Pos2 = C2->GetPos();
2691 int D1 = Max(abs(Pos1.X - PlayerPos.X), abs(Pos1.Y - PlayerPos.Y));
2692 int D2 = Max(abs(Pos2.X - PlayerPos.X), abs(Pos2.Y - PlayerPos.Y));
2693 if (D1 != D2) return D1 < D2;
2694 if (Pos1.Y != Pos2.Y) return Pos1.Y < Pos2.Y;
2695 return Pos1.X < Pos2.X;
2699 truth game::FillPetVector (cchar *Verb) {
2700 PetVector.clear();
2701 team *Team = GetTeam(PLAYER_TEAM);
2702 for (std::list<character*>::const_iterator i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i)
2703 if ((*i)->IsEnabled() && !(*i)->IsPlayer() && (*i)->CanBeSeenByPlayer()) PetVector.push_back(*i);
2704 if (PetVector.empty()) {
2705 ADD_MESSAGE("You don't detect any friends to %s.", Verb);
2706 return false;
2708 std::sort(PetVector.begin(), PetVector.end(), DistanceOrderer);
2709 LastPetUnderCursor = PetVector[0];
2710 return true;
2714 truth game::CommandQuestion () {
2715 if (!FillPetVector("command")) return false;
2716 character *Char;
2717 if (PetVector.size() == 1) Char = PetVector[0];
2718 else {
2719 v2 Pos = PetVector[0]->GetPos();
2720 Pos = PositionQuestion(CONST_S("Whom do you wish to command? [direction keys/'+'/'-'/'a'll/space/esc]"), Pos, &PetHandler, &CommandKeyHandler);
2721 if (Pos == ERROR_V2) return false;
2722 if (Pos == ABORT_V2) return true;
2723 Char = CurrentArea->GetSquare(Pos)->GetCharacter();
2724 if (!Char || !Char->CanBeSeenByPlayer()) {
2725 ADD_MESSAGE("You don't see anyone here to command.");
2726 return false;
2728 if (Char->IsPlayer()) {
2729 ADD_MESSAGE("You do that all the time.");
2730 return false;
2732 if (!Char->IsPet()) {
2733 ADD_MESSAGE("%s refuses to be commanded by you.", Char->CHAR_NAME(DEFINITE));
2734 return false;
2737 return Char->IssuePetCommands();
2741 void game::NameQuestion () {
2742 if (!FillPetVector("name")) return;
2743 if (PetVector.size() == 1) PetVector[0]->TryToName();
2744 else PositionQuestion(CONST_S("Who do you want to name? [direction keys/'+'/'-'/'n'ame/esc]"), PetVector[0]->GetPos(), &PetHandler, &NameKeyHandler);
2748 void game::PetHandler (v2 CursorPos) {
2749 character *Char = CurrentArea->GetSquare(CursorPos)->GetCharacter();
2750 if (Char && Char->CanBeSeenByPlayer() && Char->IsPet() && !Char->IsPlayer()) CursorData = RED_CURSOR|CURSOR_TARGET;
2751 else CursorData = RED_CURSOR;
2752 if (Char && !Char->IsPlayer() && Char->IsPet()) LastPetUnderCursor = Char;
2756 v2 game::CommandKeyHandler (v2 CursorPos, int Key) {
2757 if (SelectPet(Key)) return LastPetUnderCursor->GetPos();
2758 if (Key == 'a' || Key == 'A') return CommandAll() ? ABORT_V2 : ERROR_V2;
2759 return CursorPos;
2763 truth game::SelectPet (int Key) {
2764 if (Key == '+') {
2765 for (uInt c = 0; c < PetVector.size(); ++c) {
2766 if (PetVector[c] == LastPetUnderCursor) {
2767 if (++c == PetVector.size()) c = 0;
2768 LastPetUnderCursor = PetVector[c];
2769 return true;
2772 } else if (Key == '-') {
2773 for (uInt c = 0; c < PetVector.size(); ++c) {
2774 if (PetVector[c] == LastPetUnderCursor) {
2775 if (!c) c = PetVector.size();
2776 LastPetUnderCursor = PetVector[--c];
2777 return true;
2781 return false;
2785 void game::CommandScreen (cfestring &Topic, feuLong PossibleFlags, feuLong ConstantFlags, feuLong &VaryFlags, feuLong &Flags) {
2786 static cchar *CommandDescription[COMMAND_FLAGS] = {
2787 "Follow me",
2788 "Flee from enemies",
2789 "Don't change your equipment",
2790 "Don't consume anything valuable"
2792 felist List(Topic);
2793 SetStandardListAttributes(List);
2794 List.AddFlags(SELECTABLE);
2795 List.AddDescription(CONST_S(""));
2796 List.AddDescription(CONST_S("Command Active?"));
2797 for (;;) {
2798 int c, i;
2799 for (c = 0; c < COMMAND_FLAGS; ++c) {
2800 if (1 << c & PossibleFlags) {
2801 truth Changeable = !(1 << c & ConstantFlags);
2802 festring Entry;
2803 if (Changeable) {
2804 Entry = CommandDescription[c];
2805 Entry.Resize(60);
2806 } else {
2807 Entry << " " << CommandDescription[c];
2808 Entry.Resize(63);
2810 if (1 << c & VaryFlags) Entry << "varies"; else Entry << (1 << c & Flags ? "yes" : "no");
2811 List.AddEntry(Entry, Changeable ? LIGHT_GRAY : DARK_GRAY, 0, NO_IMAGE, Changeable);
2814 int Chosen = List.Draw();
2815 if (Chosen & FELIST_ERROR_BIT) return;
2816 for (c = 0, i = 0; c < COMMAND_FLAGS; ++c) {
2817 if (1 << c & PossibleFlags && !(1 << c & ConstantFlags) && i++ == Chosen) {
2818 if (1 << c & VaryFlags) {
2819 VaryFlags &= ~(1 << c);
2820 Flags |= 1 << c;
2821 } else Flags ^= 1 << c;
2822 break;
2825 List.Empty();
2826 DrawEverythingNoBlit();
2831 truth game::CommandAll () {
2832 feuLong PossibleFlags = 0, ConstantFlags = ALL_COMMAND_FLAGS, VaryFlags = 0, OldFlags = 0;
2833 uInt c1, c2;
2834 for (c1 = 0; c1 < PetVector.size(); ++c1) {
2835 ConstantFlags &= PetVector[c1]->GetConstantCommandFlags();
2836 feuLong C = PetVector[c1]->GetCommandFlags();
2837 feuLong ThisPossible = PetVector[c1]->GetPossibleCommandFlags();
2838 for (c2 = 0; c2 < COMMAND_FLAGS; ++c2)
2839 if (1 << c2 & PossibleFlags & ThisPossible && (1 << c2 & C) != (1 << c2 & OldFlags)) VaryFlags |= 1 << c2;
2840 PossibleFlags |= ThisPossible;
2841 OldFlags |= C & ThisPossible;
2843 if (!PossibleFlags) {
2844 ADD_MESSAGE("Not a single creature in your visible team can be commanded.");
2845 return false;
2847 feuLong NewFlags = OldFlags;
2848 CommandScreen(CONST_S("Issue commands to whole visible team"), PossibleFlags, ConstantFlags, VaryFlags, NewFlags);
2849 truth Change = false;
2850 for (c1 = 0; c1 < PetVector.size(); ++c1) {
2851 character *Char = PetVector[c1];
2852 if (!Char->IsConscious()) continue;
2853 feuLong OldC = Char->GetCommandFlags();
2854 feuLong ConstC = Char->GetConstantCommandFlags();
2855 feuLong ThisC = (NewFlags & Char->GetPossibleCommandFlags() & ~(ConstC|VaryFlags)) | (OldC & (ConstC|VaryFlags));
2856 if (ThisC != OldC) Change = true;
2857 Char->SetCommandFlags(ThisC);
2859 if (!Change) return false;
2860 Player->EditAP(-500);
2861 Player->EditExperience(CHARISMA, 50, 1 << 7);
2862 return true;
2866 col16 game::GetAttributeColor (int I) {
2867 int Delta = GetTick()-LastAttributeChangeTick[I];
2868 if (OldAttribute[I] == NewAttribute[I] || Delta >= 510) return WHITE;
2869 if (OldAttribute[I] < NewAttribute[I]) return MakeRGB16(255, 255, Delta >> 1);
2870 return MakeRGB16(255, Delta >> 1, Delta >> 1);
2874 void game::UpdateAttributeMemory () {
2875 for (int c = 0; c < ATTRIBUTES; ++c) {
2876 int A = Player->GetAttribute(c);
2877 if (A != NewAttribute[c]) {
2878 OldAttribute[c] = NewAttribute[c];
2879 NewAttribute[c] = A;
2880 LastAttributeChangeTick[c] = GetTick();
2886 void game::InitAttributeMemory () {
2887 for (int c = 0; c < ATTRIBUTES; ++c) OldAttribute[c] = NewAttribute[c] = Player->GetAttribute(c);
2891 void game::TeleportHandler (v2 CursorPos) {
2892 if ((CursorPos-Player->GetPos()).GetLengthSquare() > Player->GetTeleportRangeSquare())
2893 CursorData = BLUE_CURSOR|CURSOR_TARGET;
2894 else
2895 CursorData = RED_CURSOR|CURSOR_TARGET;
2899 double game::GetGameSituationDanger () {
2900 double SituationDanger = 0;
2901 character *Player = GetPlayer();
2902 truth PlayerStuck = Player->IsStuck();
2903 v2 PlayerPos = Player->GetPos();
2904 character *TruePlayer = Player;
2905 if (PlayerStuck) (Player = Player->Duplicate(IGNORE_PROHIBITIONS))->ChangeTeam(0);
2906 for (int c1 = 0; c1 < GetTeams(); ++c1)
2907 if (GetTeam(c1)->GetRelation(GetTeam(PLAYER_TEAM)) == HOSTILE)
2908 for (std::list<character*>::const_iterator i1 = GetTeam(c1)->GetMember().begin(); i1 != GetTeam(c1)->GetMember().end(); ++i1) {
2909 character *Enemy = *i1;
2910 if (Enemy->IsEnabled() && Enemy->CanAttack() && (Enemy->CanMove() || Enemy->GetPos().IsAdjacent(PlayerPos))) {
2911 truth EnemyStuck = Enemy->IsStuck();
2912 v2 EnemyPos = Enemy->GetPos();
2913 truth Sees = TruePlayer->CanBeSeenBy(Enemy);
2914 character *TrueEnemy = Enemy;
2915 if (EnemyStuck) Enemy = Enemy->Duplicate(IGNORE_PROHIBITIONS);
2916 double PlayerTeamDanger = 1/Enemy->GetSituationDanger(Player, EnemyPos, PlayerPos, Sees);
2917 for (int c2 = 0; c2 < GetTeams(); ++c2)
2918 if (GetTeam(c2)->GetRelation(GetTeam(c1)) == HOSTILE)
2919 for (std::list<character*>::const_iterator i2 = GetTeam(c2)->GetMember().begin(); i2 != GetTeam(c2)->GetMember().end(); ++i2) {
2920 character *Friend = *i2;
2921 if (Friend->IsEnabled() && !Friend->IsPlayer() && Friend->CanAttack() && (Friend->CanMove() || Friend->GetPos().IsAdjacent(EnemyPos))) {
2922 v2 FriendPos = Friend->GetPos();
2923 truth Sees = TrueEnemy->CanBeSeenBy(Friend);
2924 if (Friend->IsStuck()) {
2925 Friend = Friend->Duplicate(IGNORE_PROHIBITIONS);
2926 PlayerTeamDanger += Friend->GetSituationDanger(Enemy, FriendPos, EnemyPos, Sees) * .2;
2927 delete Friend;
2928 } else PlayerTeamDanger += Friend->GetSituationDanger(Enemy, FriendPos, EnemyPos, Sees);
2931 if (EnemyStuck) {
2932 PlayerTeamDanger *= 5;
2933 delete Enemy;
2935 SituationDanger += 1 / PlayerTeamDanger;
2938 Player->ModifySituationDanger(SituationDanger);
2939 if (PlayerStuck) {
2940 SituationDanger *= 2;
2941 delete Player;
2943 return SituationDanger;
2947 sLong game::GetTimeSpent () {
2948 return time::TimeAdd(time::TimeDifference(time(0),LastLoad), TimePlayedBeforeLastLoad);
2952 outputfile &operator << (outputfile &SaveFile, const massacreid &MI) {
2953 SaveFile << MI.Type << MI.Config << MI.Name;
2954 return SaveFile;
2958 inputfile &operator >> (inputfile &SaveFile, massacreid &MI) {
2959 SaveFile >> MI.Type >> MI.Config >> MI.Name;
2960 return SaveFile;
2964 truth game::PlayerIsRunning () {
2965 return PlayerRunning && Player->CanMove();
2969 void game::AddSpecialCursor (v2 Pos, int Data) {
2970 SpecialCursorPos.push_back(Pos);
2971 SpecialCursorData.push_back(Data);
2975 void game::RemoveSpecialCursors () {
2976 SpecialCursorPos.clear();
2977 SpecialCursorData.clear();
2981 void game::LearnAbout (god *Who) {
2982 Who->SetIsKnown(true);
2983 /* slightly slow, but doesn't matter since this is run so rarely */
2984 if (PlayerKnowsAllGods() && !game::PlayerHasReceivedAllGodsKnownBonus) {
2985 GetPlayer()->ApplyAllGodsKnownBonus();
2986 game::PlayerHasReceivedAllGodsKnownBonus = true;
2991 truth game::PlayerKnowsAllGods () {
2992 for (int c = 1; c <= GODS; ++c) if (!GetGod(c)->IsKnown()) return false;
2993 return true;
2997 void game::AdjustRelationsToAllGods (int Amount) {
2998 for (int c = 1; c <= GODS; ++c) GetGod(c)->AdjustRelation(Amount);
3002 void game::SetRelationsToAllGods (int Amount) {
3003 for (int c = 1; c <= GODS; ++c) GetGod(c)->SetRelation(Amount);
3007 void game::ShowDeathSmiley (bitmap *Buffer, truth) {
3008 static blitdata B = {
3010 { 0, 0 },
3011 { (RES.X >> 1) - 24, RES.Y * 4 / 7 - 24 },
3012 { 48, 48 },
3013 { 0 },
3014 TRANSPARENT_COLOR,
3017 int Tick = globalwindowhandler::UpdateTick();
3018 if (((Tick >> 1) & 31) == 1) B.Src.X = 48;
3019 else if (((Tick >> 1) & 31) == 2) B.Src.X = 96;
3020 else B.Src.X = 0;
3021 B.Bitmap = Buffer;
3022 igraph::GetSmileyGraphic()->NormalBlit(B);
3023 if (Buffer == DOUBLE_BUFFER) graphics::BlitDBToScreen();
3027 static int doListSelector (felist &list, int defsel, int cnt) {
3028 game::SetStandardListAttributes(list);
3029 list.AddFlags(SELECTABLE | FELIST_NO_BADKEY_EXIT);
3030 if (defsel > 0) list.SetSelected(defsel);
3031 uInt sel = list.Draw();
3032 list.Empty();
3033 list.RemoveFlags(SELECTABLE | FELIST_NO_BADKEY_EXIT);
3034 if (sel & FELIST_ERROR_BIT) return -1;
3035 if (sel >= (uInt)cnt) return -1;
3036 return (int)sel;
3040 int game::ListSelector (int defsel, cfestring &title, ...) {
3041 int cnt = 0;
3042 va_list items;
3043 va_start(items, title);
3045 felist list(title);
3046 for (;;) {
3047 const char *s = va_arg(items, const char *);
3048 if (!s) break;
3049 list.AddEntry(s, LIGHT_GRAY);
3050 cnt++;
3052 va_end(items);
3053 return doListSelector(list, defsel, cnt);
3057 int game::ListSelectorArray (int defsel, cfestring &title, const char *items[]) {
3058 int cnt = 0;
3059 felist list(title);
3060 for (;;) {
3061 if (!items[cnt]) break;
3062 list.AddEntry(items[cnt], LIGHT_GRAY);
3063 cnt++;
3065 return doListSelector(list, defsel, cnt);
3069 void game::ClearEventData () {
3070 mChar = 0;
3071 mActor = 0;
3072 mSecondActor = 0;
3073 mItem = 0;
3077 // '.': string or number
3078 // 'n': number
3079 // 's': string
3080 // '*': collect all args
3081 int game::ParseFuncArgs (cfestring &types, std::vector<FuncArg> &args, inputfile *fl, truth noterm) {
3082 festring s;
3083 sLong n;
3084 truth isStr;
3085 if (!fl) fl = mFEStack.top();
3086 args.clear();
3087 for (unsigned int f = 0; f < types.GetSize(); f++) {
3088 switch (types[f]) {
3089 case '.':
3090 s = fl->ReadStringOrNumber(&n, &isStr, true);
3091 if (isStr) args.push_back(FuncArg(s)); else args.push_back(FuncArg(n));
3092 break;
3093 case 'n':
3094 n = fl->ReadNumber(0xFF, true);
3095 args.push_back(FuncArg(n));
3096 break;
3097 case '*':
3098 for (;;) {
3099 s = fl->ReadStringOrNumber(&n, &isStr, true);
3100 if (isStr) args.push_back(FuncArg(s)); else args.push_back(FuncArg(n));
3101 fl->ReadWord(s, true);
3102 if (s == ";") return args.size();
3103 if (s != ",") ABORT("',' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TellLine());
3105 // never reached
3106 case 's':
3107 default:
3108 s = fl->ReadWord(true);
3109 args.push_back(FuncArg(s));
3110 break;
3112 if (f == types.GetSize()-1) {
3113 if (noterm) break;
3114 fl->ReadWord(s, true);
3115 if (s != ";") ABORT("';' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TellLine());
3116 break;
3117 } else {
3118 fl->ReadWord(s, true);
3119 if (s != ",") ABORT("',' expected in file %s line %d!", fl->GetFileName().CStr(), fl->TellLine());
3122 return args.size();
3126 truth game::GetWord (festring &w) {
3127 for (;;) {
3128 inputfile *fl = mFEStack.top();
3129 fl->ReadWord(w, false);
3130 if (w == "" && fl->Eof()) {
3131 delete fl;
3132 mFEStack.pop();
3133 if (mFEStack.empty()) return false;
3134 continue;
3136 if (w == "Include") {
3137 fl->ReadWord(w, true);
3138 if (fl->ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl->GetFileName().CStr(), fl->TellLine());
3139 w = game::GetGameDir()+"Script/"+w;
3140 inputfile *fl = new inputfile(w, &game::GetGlobalValueMap(), true);
3141 fl->setGetVarCB(game::ldrGetVar);
3142 mFEStack.push(fl);
3143 continue;
3145 if (w == "Message") {
3146 fl->ReadWord(w, true);
3147 if (fl->ReadWord() != ";") ABORT("Invalid terminator in file %s at line %d!", fl->GetFileName().CStr(), fl->TellLine());
3148 fprintf(stderr, "MESSAGE: %s\n", w.CStr());
3149 continue;
3151 return true;
3156 void game::SkipBlock (truth brcEaten) {
3157 festring w;
3158 if (!brcEaten) {
3159 mFEStack.top()->ReadWord(w, true);
3160 if (w != "{") ABORT("'{' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TellLine());
3162 int cnt = 1;
3163 for (;;) {
3164 mFEStack.top()->ReadWord(w, true);
3165 if (w == "{") cnt++;
3166 else if (w == "}") {
3167 if (--cnt < 1) break;
3173 truth game::DoOnEvent (truth brcEaten, truth AllowScript) {
3174 // do; only funcalls for now
3175 truth eaten = AllowScript ? true : false;
3176 festring w;
3177 if (!brcEaten) {
3178 mFEStack.top()->ReadWord(w, true);
3179 if (w != "{") ABORT("'{' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TellLine());
3181 for (;;) {
3182 if (!GetWord(w)) {
3183 if (AllowScript) break;
3184 ABORT("Unexpected end of file %s!", mFEStack.top()->GetFileName().CStr());
3186 //fprintf(stderr, " :[%s]\n", w.CStr());
3187 if (w == "}") {
3188 if (AllowScript) ABORT("Unexpected '}' in AllowScript file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TellLine());
3189 break;
3191 if (w == ";") continue;
3192 if (w == "@") {
3193 mFEStack.top()->ReadWord(w, true);
3194 if (mFEStack.top()->ReadWord(true) != "=") ABORT("'=' expected in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TellLine());
3195 //fprintf(stderr, "setvar: %s\n", w.CStr());
3196 if (w == "money") {
3197 sLong n = mFEStack.top()->ReadNumber(true);
3198 if (n < 0) n = 0;
3199 if (mChar) mChar->SetMoney(n);
3200 continue;
3202 if (w == "result") {
3203 mResult = mFEStack.top()->ReadNumber(true);
3204 continue;
3206 ABORT("Unknown var [%s] in file %s at line %d!", w.CStr(), mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TellLine());
3207 } else {
3208 //mFEStack.top()->ReadWord(w, true);
3209 std::vector<FuncArg> args;
3210 //fprintf(stderr, "funcall: %s\n", w.CStr());
3211 if (w == "SetMoney") {
3212 ParseFuncArgs("n", args);
3213 sLong n = args[0].ival;
3214 if (n < 0) n = 0;
3215 if (mChar) mChar->SetMoney(n);
3216 continue;
3218 if (w == "EditMoney") {
3219 ParseFuncArgs("n", args);
3220 sLong n = args[0].ival;
3221 if (mChar) mChar->EditMoney(n);
3222 continue;
3224 if (w == "AddMessage") {
3225 ParseFuncArgs("*", args);
3226 festring s;
3227 for (uInt f = 0; f < args.size(); f++) {
3228 const FuncArg &a = args[f];
3229 if (a.type == FARG_STRING) s << a.sval; else s << a.ival;
3231 ADD_MESSAGE("%s", s.CStr());
3232 continue;
3234 if (w == "EatThisEvent") {
3235 if (AllowScript) ABORT("'EatThisEvent' forbidden in AllowScripts in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TellLine());
3236 eaten = true;
3237 continue;
3239 if (w == "Disallow") {
3240 if (!AllowScript) ABORT("'Disallow' forbidden in not-AllowScripts in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TellLine());
3241 eaten = false;
3242 continue;
3244 ABORT("Unknown function [%s] in file %s at line %d!", w.CStr(), mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TellLine());
3245 //if (mFEStack.top()->ReadWord() != ";") ABORT("';' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TellLine());
3247 //ABORT("Invalid term in file %s at line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TellLine());
3249 //fprintf(stderr, "------------\n");
3250 return eaten;
3254 //TODO: cache event scripts
3255 truth game::RunOnEvent (cfestring &ename) {
3256 static std::vector<festring> scriptFiles;
3257 static truth cached = false;
3258 truth res = false;
3260 character *old = mChar;
3261 mChar = PLAYER;
3262 if (!cached) {
3263 cached = true;
3264 for (int fno = 99; fno >= -1; fno--) {
3265 festring cfname;
3266 cfname << game::GetGameDir() << "Script/onevent";
3267 if (fno >= 0) {
3268 char bnum[8];
3269 sprintf(bnum, "_%02d", fno);
3270 cfname << bnum;
3272 cfname << ".dat";
3273 if (!inputfile::fileExists(cfname)) continue;
3274 inputfile *ifl = new inputfile(cfname, &game::GetGlobalValueMap(), false);
3275 if (!ifl->IsOpen()) {
3276 delete ifl;
3277 continue;
3279 scriptFiles.push_back(cfname);
3280 ifl->setGetVarCB(game::ldrGetVar);
3281 mFEStack.push(ifl);
3283 } else {
3284 for (unsigned int f = 0; f < scriptFiles.size(); ++f) {
3285 festring cfname = scriptFiles[f];
3286 inputfile *ifl = new inputfile(cfname, &game::GetGlobalValueMap(), false);
3287 if (!ifl->IsOpen()) {
3288 delete ifl;
3289 continue;
3291 ifl->setGetVarCB(game::ldrGetVar);
3292 mFEStack.push(ifl);
3296 festring w;
3297 while (GetWord(w)) {
3298 if (w != "on") ABORT("'on' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TellLine());
3299 mFEStack.top()->ReadWord(w, true);
3300 truth doIt = (w==ename);
3301 if (doIt && !res) {
3302 res = DoOnEvent(false);
3303 } else {
3304 // skip
3305 SkipBlock(false);
3308 mChar = old;
3309 return res;
3313 truth game::RunOnEventStr (cfestring &ename, cfestring &str) {
3314 truth res = false;
3315 if (str.GetSize() < 1) return false;
3316 //fprintf(stderr, "=============\n%s=============\n", str.CStr());
3317 inputfile *ifl = new meminputfile(str, &game::GetGlobalValueMap());
3318 ifl->setGetVarCB(game::ldrGetVar);
3319 mFEStack.push(ifl);
3320 festring w;
3321 //fprintf(stderr, "=============\n", str.CStr());
3322 //fprintf(stderr, "event: [%s]\n", ename.CStr());
3323 //fprintf(stderr, "---\n%s---\n", str.CStr());
3324 while (GetWord(w)) {
3325 if (w != "on") ABORT("'on' expected in file %s line %d!", mFEStack.top()->GetFileName().CStr(), mFEStack.top()->TellLine());
3326 mFEStack.top()->ReadWord(w, true);
3327 //fprintf(stderr, "on: [%s]\n", w.CStr());
3328 truth doIt = (w==ename);
3329 if (doIt && !res) {
3330 //fprintf(stderr, " do it\n");
3331 res = DoOnEvent(false);
3332 } else {
3333 // skip
3334 //fprintf(stderr, " skip it\n");
3335 SkipBlock(false);
3338 return res;
3342 truth game::RunOnCharEvent (character *who, cfestring &ename) {
3343 truth res = false;
3344 if (!who) return false;
3345 character *old = mChar;
3346 mChar = who;
3347 res = RunOnEventStr(ename, who->mOnEvents);
3348 if (!res) res = RunOnEventStr(ename, who->GetProtoType()->mOnEvents);
3349 mChar = old;
3350 return res;
3354 truth game::RunOnItemEvent (item *what, cfestring &ename) {
3355 truth res = false;
3356 if (!what) return false;
3357 item *old = mItem;
3358 mItem = what;
3359 res = RunOnEventStr(ename, what->mOnEvents);
3360 if (!res) res = RunOnEventStr(ename, what->GetProtoType()->mOnEvents);
3361 mItem = old;
3362 return res;
3366 festring game::ldrGetVar (inputfile *fl, cfestring &name) {
3367 //fprintf(stderr, "GETVAR: [%s]\n", name.CStr());
3368 if (name == "player_name") {
3369 return game::GetPlayerName();
3371 if (name == "money") {
3372 festring res;
3373 if (!mChar) return "0";
3374 res << mChar->GetMoney();
3375 return res;
3377 if (name == "name") {
3378 if (!mChar) return "";
3379 return mChar->GetAssignedName();
3381 if (name == "team") {
3382 festring res;
3383 if (!mChar) return "";
3384 res << mChar->GetTeam()->GetID();
3385 return res;
3387 if (name == "friendly") {
3388 festring res;
3389 if (!mChar || !PLAYER || mChar->GetRelation(PLAYER) != HOSTILE) return "tan";
3390 return "";
3392 if (name == "hostile") {
3393 festring res;
3394 if (!mChar || !PLAYER) return "";
3395 if (mChar->GetRelation(PLAYER) == HOSTILE) return "tan";
3396 return "";
3398 if (name == "has_item") {
3399 std::vector<FuncArg> args;
3400 ParseFuncArgs("s", args, fl, true);
3402 if (PLAYER) {
3403 itemvector items;
3404 festring s = args[0].sval;
3406 //fprintf(stderr, "looking for [%s]\n", s.CStr());
3407 PLAYER->GetStack()->FillItemVector(items);
3408 for (unsigned int f = 0; f < items.size(); ++f) {
3409 for (uInt c = 0; c < items[f]->GetDataBase()->Alias.Size; ++c) {
3410 //fprintf(stderr, "%u:%u: [%s]\n", f, c, items[f]->GetDataBase()->Alias[c].CStr());
3411 if (s.CompareIgnoreCase(items[f]->GetDataBase()->Alias[c]) == 0) {
3412 //fprintf(stderr, " FOUND!\n");
3413 return "tan";
3417 //fprintf(stderr, "checking equipment...\n");
3418 for (int f = 0; f < PLAYER->GetEquipments(); ++f) {
3419 item *it = PLAYER->GetEquipment(f);
3421 if (it) {
3422 for (uInt c = 0; c < it->GetDataBase()->Alias.Size; ++c) {
3423 //fprintf(stderr, "%u:%u: [%s]\n", f, c, it->GetDataBase()->Alias[c].CStr());
3424 if (s.CompareIgnoreCase(it->GetDataBase()->Alias[c]) == 0) {
3425 //fprintf(stderr, " FOUND!\n");
3426 return "tan";
3432 return "";
3434 //if (name == "type") return mVarType;
3435 ABORT("unknown variable: %s", name.CStr());
3436 return "";
3440 truth game::CheckDropLeftover (item *i) {
3441 if (i->IsBottle() && !ivanconfig::GetAutoDropBottles()) return false;
3442 if (i->IsCan() && !ivanconfig::GetAutoDropCans()) return false;
3443 if (!ivanconfig::GetAutoDropLeftOvers()) return false;
3444 return true;
3448 truth game::RunAllowScriptStr (cfestring &str) {
3449 truth res = true;
3450 if (str.GetSize() < 1) return true;
3451 //fprintf(stderr, "====\n%s\n====\n", str.CStr());
3452 inputfile *ifl = new meminputfile(str, &game::GetGlobalValueMap());
3453 ifl->setGetVarCB(game::ldrGetVar);
3454 mFEStack.push(ifl);
3455 res = DoOnEvent(true, true);
3456 //fprintf(stderr, "mFEStack: %u\n", mFEStack.size());
3457 return res;