// Copyright © 2016-2017 Graham Sanderson, all rights reserved. #include "lith_common.h" #include "lith_player.h" #include "lith_world.h" #include "lith_monster.h" #include "lith_version.h" #include #define InSecret \ (world.game == Game_Doom2 && (world.cluster == 9 || world.cluster == 10)) #define InHell \ (world.game == Game_Doom2 && world.cluster >= 8) #define OnEarth \ (world.game == Game_Doom2 && world.cluster == 7) // Extern Objects ------------------------------------------------------------| __addrdef __mod_arr lmvar; __addrdef __hub_arr lwvar; bool lmvar mapinit; int lmvar mapid; worldinfo_t world = {.apiversion = Lith_APIVersion}; payoutinfo_t payout; // Static Objects ------------------------------------------------------------| static bool lmvar rain_chk; static fixed lmvar rain_px; static fixed lmvar rain_py; static int lmvar rain_dist; static bool reopen; static bool dorain; // Extern Functions ----------------------------------------------------------| script acs void Lith_SpawnBosses(i96 sum, bool force); // // Lith_GetWorldExtern // worldinfo_t *Lith_GetWorldExtern(void) { return &world; } // // Lith_FreezeTime // stkcall void Lith_FreezeTime(bool on) { static int lmvar frozen; if(on) { if(!frozen++) { Lith_ForPlayer() { p->frozen++; p->setVel(0, 0, 0); } Lith_ForPlayer() { Lith_GiveActorInventory(p->tid, "Lith_TimeHax", 1); Lith_GiveActorInventory(p->tid, "Lith_TimeHax2", 1); break; } } } else { if(!--frozen) { Lith_ForPlayer() p->frozen--; Lith_ForPlayer() { Lith_TakeActorInventory(p->tid, "PowerTimeFreezer", 1); Lith_TakeActorInventory(p->tid, "Lith_TimeHax2", 1); break; } } } } // // Lith_BeginAngles // stkcall void Lith_BeginAngles(int x, int y) { world.a_cur = 0; for(int i = 0; i < countof(world.a_angles); i++) { world.a_angles[i].ang = 0; world.a_angles[i].dst = 0; } world.a_x = x; world.a_y = y; } // // Lith_AddAngle // stkcall fixed Lith_AddAngle(int x, int y) { if(world.a_cur >= countof(world.a_angles)) return 0; struct polar *p = &world.a_angles[world.a_cur++]; *p = ctopol(x - world.a_x, y - world.a_y); return p->ang; } // // Lith_AddAngleScript // script acs address("Lith_AddAngle") void Lith_AddAngleScript(int x, int y) { Lith_AddAngle(x, y); } // // Lith_CanonTime // __str Lith_CanonTime(int type) { int seconds = 53 + (world.ticks / 35); int minutes = 30 + (seconds / 60); int hours = 14 + (minutes / 60); int days = 25 + (hours / 24); // pls switch(type) { case CANONTIME_FULL: return StrParam("%0.2i:%0.2i:%0.2i %i-7-1649", hours % 24, minutes % 60, seconds % 60, days); case CANONTIME_SHORT: return StrParam("%0.2i:%0.2i %i-7-49", hours % 24, minutes % 60, days); case CANONTIME_DATE: return StrParam("%i-7-1649", days); } } // // Lith_UniqueID // int Lith_UniqueID(int tid) { int pn; // Negative values are for players. if((pn = Lith_GetPlayerNumber(tid)) != -1) return -(pn + 1); // If we already have a unique identifier, return that. int id = Lith_CheckActorInventory(tid, "Lith_UniqueID"); // Otherwise we have to give a new unique identifier. if(id == 0) Lith_GiveActorInventory(tid, "Lith_UniqueID", id = ++mapid); return id; } // // Lith_EmitScore // script acs void Lith_EmitScore(int amount) { Lith_GiveAllScore(amount, false); } // // Lith_EmitEXP // script acs void Lith_EmitEXP(int amount) { Lith_GiveAllEXP(amount); } // // Lith_MakeSerious // script acs void Lith_MakeSerious() { ACS_SetActorProperty(0, APROP_RenderStyle, STYLE_None); ACS_SetActorPropertyString(0, APROP_ActiveSound, "silence"); ACS_SetActorPropertyString(0, APROP_DeathSound, "silence"); ACS_SetActorPropertyString(0, APROP_PainSound, "silence"); } // // LPData // script acs int LPData(int info, int permutation, bool target) { if(target) ACS_SetActivatorToTarget(0); withplayer(LocalPlayer) switch(info) { case pdata_upgrade: return p->getUpgrActive(permutation); case pdata_buttons: return p->buttons; case pdata_rifle_firemode: return p->riflefiremode; case pdata_has_sigil: return p->sigil.acquired; case pdata_weapon_zoom: return bitsk(p->getCVarK("lith_weapons_zoomfactor")); case pdata_pclass: return p->pclass; } return 0; } // // LWData // script acs int LWData(int info) { switch(info) { case wdata_bossspawned: return world.bossspawned; case wdata_enemycheck: return world.enemycheck; case wdata_ptid: Lith_ForPlayer() return p->tid; case wdata_pclass: Lith_ForPlayer() return p->pclass; case wdata_magdrops: Lith_ForPlayer() return p->getCVarI("lith_weapons_magdrops"); } return 0; } // // Lith_RainDropSpawn // script acs void Lith_RainDropSpawn() { if(rain_chk) { int dist = mag2i(ACS_GetActorX(0) - rain_px, ACS_GetActorY(0) - rain_py); if(dist < rain_dist) rain_dist = dist; } } // Static Functions ----------------------------------------------------------| // // DoRain // script static void DoRain() { // Doesn't work in multiplayer, sorry! if(ACS_PlayerCount() > 1) return; dorain = true; struct player *p = &players[0]; p->setActivator(); ACS_PlaySound(p->weathertid, "amb/wind", CHAN_BODY, 0.001, true, ATTN_NONE); ACS_PlaySound(p->weathertid, "amb/rain", CHAN_VOICE, 0.001, true, ATTN_NONE); fixed skydist, curskydist = 1; for(;;) { if((rain_chk = !ACS_CheckActorCeilingTexture(0, "F_SKY1"))) { rain_dist = 1024; rain_px = p->x; rain_py = p->y; } else InvTake("Lith_SMGHeat", 1); if((InHell || InSecret) && !world.islithmap) ACS_GiveActorInventory(p->tid, "Lith_SpawnBloodRain", 1); else ACS_GiveActorInventory(p->tid, "Lith_SpawnRain", 1); ACS_Delay(1); if(rain_chk) { skydist = rain_dist / 1024.0; skydist = minmax(skydist, 0, 1); } else skydist = 0; curskydist = lerpk(curskydist, skydist, 0.035); ACS_SoundVolume(p->weathertid, CHAN_BODY, 1 - curskydist); ACS_SoundVolume(p->weathertid, CHAN_VOICE, 1 - curskydist); } } // // DoPayout // static void DoPayout(void) { fixed64 taxpct = ACS_RandomFixed(0, 4 / 100.0); #define GenPay(name) \ if(payout.name##max) \ { \ payout.name##pct = (payout.name##num / (fixed64)payout.name##max) * 100; \ payout.name##scr = payout.name##pct * 600; \ } GenPay(kill) GenPay(item) #undef GenPay payout.total = payout.killscr + payout.itemscr; payout.total -= payout.tax = payout.total * taxpct; Lith_ForPlayer() { script extern void Lith_PlayerPayout(struct player *p); Lith_PlayerPayout(p); } payout = (payoutinfo_t){}; } // // CheckEnemyCompat // script static void CheckEnemyCompat(void) { if(ACS_GetCVar("sv_nomonsters") || world.enemycheck) return; int tid; if(ACS_SpawnForced("ZombieMan", 0, 0, 0, tid = ACS_UniqueTID(), 0)) { ACS_SetActivator(tid); InvGive("Lith_EnemyChecker", 1); __str cl = ACS_GetActorClass(0); LogDebug(log_dev, "Enemy check on %S", cl); if(strstr_str(cl, "Lith_") || ACS_StrCmp(cl, "RLFormer", 8) == 0) world.enemycompat = true; if(world.enemycompat) LogDebug(log_dev, "Enemies are \Cdcompatible"); else LogDebug(log_dev, "Enemies are \Cgnot compatible"); world.enemycheck = true; ACS_Thing_Remove(0); } } // // SpawnBoss // script static void SpawnBoss() { ACS_Delay(1); // Delay another tic for monster spawners. Lith_ForPlayer() { Lith_SpawnBosses(p->scoresum, false); break; } } // // GetDebugInfo // static void GetDebugInfo(void) { bool all = ACS_GetCVar("__lith_debug_all"); world.dbgLevel = ACS_GetCVar("__lith_debug_level"); world.dbgItems = all || ACS_GetCVar("__lith_debug_items"); world.dbgBIP = all || ACS_GetCVar("__lith_debug_bip"); world.dbgScore = all || ACS_GetCVar("__lith_debug_score"); world.dbgUpgr = all || ACS_GetCVar("__lith_debug_upgrades"); world.dbgSave = all || ACS_GetCVar("__lith_debug_save"); world.dbgNoMon = ACS_GetCVar("__lith_debug_nomonsters"); } // // CheckModCompat // static void CheckModCompat(void) { int tid; if((world.legendoom = ACS_SpawnForced("LDLegendaryMonsterMarker", 0, 0, 0, tid = ACS_UniqueTID(), 0))) ACS_Thing_Remove(tid); world.drlamonsters = ACS_GetCVar("DRLA_is_using_monsters"); } // // UpdateGame // static void UpdateGame(void) { #define Update(n) \ if(ACS_GetCVarFixed("__lith_version") < n) \ __with(ACS_SetCVarFixed("__lith_version", n);) Update(15.1) ACS_SetCVarFixed("lith_sv_scoremul", 1.25); // 2.0=>1.25 Update(15.2) ACS_SetCVar("lith_sv_difficulty", 10); // 1=>10 Update(16.0) Lith_ForPlayer() p->setCVarK("lith_player_footstepvol", 0.2); // 1.0=>0.2 #undef Update } // // GSInit // static void GSInit(void) { extern void Lith_GInit_Shop(void); extern void Lith_GSReinit_Upgrade(void); extern void Lith_GSInit_Upgrade(void); extern void Lith_GSInit_Weapon(void); extern void Lith_GSInit_Dialogue(void); extern void Lith_InstallCBIItem(int num); LogDebug(log_dev, "GINIT RUNNING"); CheckModCompat(); UpdateGame(); GetDebugInfo(); Lith_GInit_Shop(); if(!world.gsinit) { LogDebug(log_dev, "GSINIT RUNNING"); Lith_GSInit_Upgrade(); Lith_GSInit_Weapon(); Lith_GSInit_Dialogue(); CheckEnemyCompat(); world.game = ACS_GetCVar("__lith_game"); world.singleplayer = ACS_GameType() == GAME_SINGLE_PLAYER; world.cbiperf = 10; if(ACS_GetCVar("lith_sv_nobosses") || world.dbgItems) for(int i = 0; i < cupg_max; i++) Lith_InstallCBIItem(i); world.gsinit = true; } else Lith_GSReinit_Upgrade(); } // // MInit // static void MInit(void) { LogDebug(log_dev, "MINIT RUNNING"); extern void Lith_LoadMapDialogue(void); Lith_LoadMapDialogue(); world.islithmap = (world.mapnum & 0xFFFFFC00) == 0x01202000; // Init a random seed from the map. world.mapseed = ACS_Random(0, 0x7FFFFFFF); // Init global score multiplier per-map. world.scoremul = round(ACS_GetCVarFixed("lith_sv_scoremul") * 10) / 10; // Give players some extra score if they're playing on extra hard or above. if(ACS_GameSkill() >= skill_extrahard) world.scoremul += 0.15; // Set the air control because ZDoom's default sucks. ACS_SetAirControl(0.77); // Set up translations. for(int i = 0; i < 8; i++) { static struct {int r,g,b;} const trs[8] = { {0xF5,0x22,0x22}, {0xF5,0x22,0xC4}, {0xC0,0x22,0xF5}, {0x50,0x22,0xF5}, {0x22,0xF5,0xF5}, {0x22,0xF5,0x8C}, {0xF5,0xF5,0x22}, {0xF5,0xB2,0x22}, }; ACS_CreateTranslationStart(42470 + i); ACS_CreateTranslationRGB(80, 111, trs[i].r, trs[i].g, trs[i].b, trs[i].r/4, trs[i].g/4, trs[i].b/4); ACS_CreateTranslationEnd(); } } // // MSInit // static void MSInit(void) { LogDebug(log_dev, "MSINIT RUNNING"); payout.killmax += world.mapkillmax; payout.itemmax += world.mapitemmax; // Line 1888300 is used as a control line for mod features. // Check for if rain should be used. if(!ACS_GetLineUDMFInt(1888300, "user_lith_norain") && (ACS_GetCVar("lith_sv_rain") || ACS_GetLineUDMFInt(1888300, "user_lith_userain"))) DoRain(); } // // WSInit // static void WSInit(void) { LogDebug(log_dev, "WSINIT RUNNING"); dmonid = 0; world.bossspawned = false; if(world.unloaded) world.mapscleared++; if(ACS_GetCVar("lith_sv_sky") && !world.islithmap) { if(InHell) { ACS_ChangeSky("LITHSKRD", "LITHSKRD"); ACS_SetSkyScrollSpeed(1, 0.01); } else if(OnEarth) ACS_ChangeSky("LITHSKDE", "LITHSKDE"); else ACS_ChangeSky("LITHSKS1", "LITHSKS1"); } } // // WInit // static void WInit(void) { LogDebug(log_dev, "WINIT RUNNING"); if(!ACS_GetCVar("lith_sv_nobosses")) SpawnBoss(); // Payout, which is not done on the first map. if(world.mapscleared != 0) DoPayout(); // Cluster messages. #define Message(cmp, n, name) \ if(world.cluster cmp && !msgs[n]) { \ Lith_ForPlayer() \ p->deliverMail(name); \ msgs[n] = true; \ } else ((void)0) static bool msgs[5]; if(world.game == Game_Doom2) { Message(>= 6, 0, "Cluster1"); Message(>= 7, 1, "Cluster2"); Message(== 8, 2, "Cluster3"); Message(== 9, 3, "Secret1"); Message(== 10, 4, "Secret2"); } } // Scripts -------------------------------------------------------------------| // // Lith_World // script type("open") static void Lith_World(void) { begin: if(ACS_GameType() == GAME_TITLE_MAP) { script extern void Lith_Title(void); Lith_Title(); return; } LogDebug(log_dev, "LITH OPEN"); if(world.mapnum == 1911777) { ACS_Exit_Normal(0); return; } dbgnotenum = 0; ACS_Delay(1); GSInit(); // Init global state. MInit(); // Map init. // World-static pre-player init. bool doworldinit = false; if(ACS_Timer() <= 2) { WSInit(); doworldinit = true; } world.unloaded = false; // World unloaded flag can be reset now. mapinit = true; // Sigil for when Lith_PlayerEntry can run. ACS_Delay(1); // Delay so players get initialized. // World-static post-player init. if(doworldinit) WInit(); // Map-static post-world init. MSInit(); // Main loop. int prevsecrets = 0; int prevkills = 0; int previtems = 0; int missionkill = 0; int missionprc = 0; for(;;) { if(reopen) { mapinit = reopen = false; goto begin; } if(world.ticks > 67 * 35 * 60 * 60 && !world.islithmap) ACS_Teleport_NewMap(1911777, 0, 0); int secrets = world.mapsecrets; int kills = world.mapkills; int items = world.mapitems; if(secrets > prevsecrets) { int delta = secrets - prevsecrets; Lith_GiveAllScore(9000 * delta, true); world.secretsfound += delta; } if(kills > prevkills) payout.killnum += kills - prevkills; if(items > previtems) payout.itemnum += items - previtems; prevsecrets = secrets; prevkills = kills; previtems = items; if(world.enemycheck && !world.dbgNoMon) { extern void DmonDebugInfo(void); ACS_SpawnForced("Lith_MonsterInfoEmitter", 0, 0, 0); DmonDebugInfo(); } if(ACS_Timer() % 5 == 0 && missionkill < kills) { if(++missionprc >= 150) { Lith_SpawnBosses(0, true); missionprc = 0; } missionkill = kills; } DebugStat("mission%%: %i\n", missionprc); ACS_Delay(1); dbgstatnum = 0; world.ticks++; if(world.autosave && world.ticks % (35 * 60 * world.autosave) == 0) ACS_Autosave(); } } // // Lith_WorldReopen // script acs void Lith_WorldReopen(void) { reopen = true; } // // Lith_WorldUnload // script type("unloading") static void Lith_WorldUnload(void) { extern void Lith_InstallSpawnedCBIItems(void); world.unloaded = true; LogDebug(log_dev, "WORLD UNLOADED"); Lith_InstallSpawnedCBIItems(); Lith_ForPlayer() { p->setActivator(); Lith_PlayerDeinitUpgrades(p); p->closeGUI(); Lith_PlayerUpdateStats(p); } } // EOF