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