diff --git a/planet/Experimental.ocd/Ambience.ocd/DefCore.txt b/planet/Experimental.ocd/Ambience.ocd/DefCore.txt new file mode 100644 index 000000000..a0dce3a09 --- /dev/null +++ b/planet/Experimental.ocd/Ambience.ocd/DefCore.txt @@ -0,0 +1,5 @@ +[DefCore] +id=Ambience +Version=5,4,0,0 +Category=C4D_StaticBack | C4D_Rule +Picture=0,0,128,128 diff --git a/planet/Experimental.ocd/Ambience.ocd/Graphics.png b/planet/Experimental.ocd/Ambience.ocd/Graphics.png new file mode 100644 index 000000000..eb56c3723 Binary files /dev/null and b/planet/Experimental.ocd/Ambience.ocd/Graphics.png differ diff --git a/planet/Experimental.ocd/Ambience.ocd/Script.c b/planet/Experimental.ocd/Ambience.ocd/Script.c new file mode 100644 index 000000000..cd524dc7a --- /dev/null +++ b/planet/Experimental.ocd/Ambience.ocd/Script.c @@ -0,0 +1,299 @@ +/** + Ambience + Controls sound and music depending on the environment the player is in + + @author Sven2 +*/ + +local exec_counter; // counter to distribute execution of players across frames +local last_environment; // array indexed by player number: pointer to environment the player was in last +local environments; // array of available environments for which it is checked if the player is in. sorted by priority. + +// Initialization +protected func Initialize() +{ + // Base environment + Environment = { + actions = [], + min_change_delay = 1, + min_initial_change_delay = 5, + AddSound = this.Env_AddSound, + AddAction = this.Env_AddAction, + SetMusic = this.Env_SetMusic + }; + // Register default environments (overloadable) + this->InitializeEnvironments(); + // Periodic execution of ambience events + last_environment = []; + AddTimer(this.Execute, 10); + return true; +} + +func InitializeEnvironments() +{ + // Register all standard environments + environments = []; + // Underwater: Clonk is swimming in water + var underwater = this.env_underwater = new Environment {}; + underwater->SetMusic("underwater"); + underwater.CheckPlayer = this.EnvCheck_Underwater; + AddEnvironment(underwater, 1400); + // City: Clonk is surrounded by buildings + var city = this.env_city = new Environment {}; + city->SetMusic("city"); + city.CheckPlayer = this.EnvCheck_City; + AddEnvironment(city, 1200); + // Lava: Lava material is nearby + var lava = this.env_lava = new Environment {}; + lava->SetMusic("lava"); + lava.CheckPlayer = this.EnvCheck_Lava; + lava.mat_mask = CreateArray(); // material mask for lava materials. +1 cuz sky. + lava.mat_mask[Material("Lava")+1] = true; // loop over materials and check incindiary instead? Whoever introduces the next lava type can do that... + lava.mat_mask[Material("DuroLava")+1] = true; + lava.min_change_delay = 3; // Easy to miss lava on search. + AddEnvironment(lava, 1000); + // Underground: Clonk in front of tunnel + var underground = this.env_underground = new Environment {}; + underground->SetMusic("underground"); + underground.CheckPlayer = this.EnvCheck_Underground; + AddEnvironment(underground, 800); + // Mountains: Overground and lots of rock around + var mountains = this.env_mountains = new Environment {}; + mountains->SetMusic("mountains"); + mountains.CheckPlayer = this.EnvCheck_Mountains; + mountains.mat_mask = CreateArray(); // material mask for mountain materials. +1 cuz sky. + mountains.mat_mask[Material("Rock")+1] = true; + mountains.mat_mask[Material("Granite")+1] = true; + mountains.mat_mask[Material("Ore")+1] = true; + mountains.mat_mask[Material("Gold")+1] = true; + mountains.min_change_delay = 3; // Pretty unstable condition + AddEnvironment(mountains, 600); + // Snow: It's snowing around the clonk + var snow = this.env_snow = new Environment {}; + snow->SetMusic("snow"); + snow.CheckPlayer = this.EnvCheck_Snow; + snow.min_change_delay = 6; // Persist a while after snowing stopped + snow.mat = Material("Snow"); + AddEnvironment(snow, 400); + // Night: Sunlight blocked by planet + var night = this.env_night = new Environment {}; + night->SetMusic("night"); + night.CheckPlayer = this.EnvCheck_Night; + AddEnvironment(night, 200); + // Overground: Default environment + var overground = this.env_overground = new Environment {}; + overground->SetMusic("overground"); + overground.CheckPlayer = this.EnvCheck_Overground; + overground->AddSound("Ding", 100); + AddEnvironment(overground, 0); + return true; +} + +private func Execute() +{ + // Per-player execution every third timer (~.8 seconds) + var i=GetPlayerCount(C4PT_User); + while (i--) if (!(++exec_counter % 3)) + { + ExecutePlayer(GetPlayerByIndex(i, C4PT_User)); + } + return true; +} + +private func ExecutePlayer(int plr) +{ + var cursor = GetCursor(plr); + // Determine environment the player is currently in + var environment = nil; + if (cursor) + { + var last_env = last_environment[plr]; + var x = cursor->GetX(), y = cursor->GetY(); + for (test_environment in environments) + { + if (environment = test_environment->CheckPlayer(cursor, x, y, test_environment == last_env)) + { + // We've found a matchign environment. + // Was it a change? Then check delays first + if (test_environment != last_env) + { + if (last_env && last_env.no_change_delay) + { + // Environment should change but a delay is specified. Keep last environment for now. + --last_env.no_change_delay; + environment = last_env; + break; + } + // New environment and change delay has passed. + environment.no_change_delay = environment.min_initial_change_delay; + Log("%s environment: %s", GetPlayerName(plr), environment.music); + } + else + { + // Was no change: Reset change delays + environment.no_change_delay = Max(environment.no_change_delay, environment.min_change_delay); + } + break; + } + } + } + last_environment[plr] = environment; + if (!environment) return true; + // Music by environment + this->SetPlayList(environment.music, plr, true, 3000); + // Sounds and actions by environment + for (var action in environment.actions) + if (Random(1000) < action.chance) + cursor->Call(action.fn, action.par[0], action.par[1], action.par[2], action.par[3], action.par[4]); + return true; +} + +func InitializePlayer(int plr) +{ + // Newly joining players should have set playlist immediately (so they don't start playing a random song just to switch it immediately) + ExecutePlayer(plr); + return true; +} + +func RemovePlayer(int plr) +{ + // Ensure newly joining players don't check on another player's environment + last_environment[plr] = nil; + return true; +} + +protected func Activate(int byplr) +{ + MessageWindow(this.Description, byplr); + return true; +} + +/* Environment functions */ + +func AddEnvironment(proplist new_env, priority) +{ + if (GetType(priority)) new_env.Priority = priority; + this.environments[GetLength(environments)] = new_env; + SortArrayByProperty(this.environments, "Priority", true); + return true; +} + +private func Env_AddSound(string snd_name, chance) +{ + return Env_AddAction(Global.Sound, snd_name, chance ?? 50); +} + +private func Env_AddAction(afn, par0, par1, par2, par3, par4) +{ + return this.actions[GetLength(this.actions)] = { fn=afn, par=[par0, par1, par2, par3, par4] }; +} + +private func Env_SetMusic(string playlist) +{ + this.music = playlist; + return true; +} + +/* Default environment checks */ + +private func EnvCheck_Underwater(object cursor, int x, int y, bool is_current) +{ + // Clonk should be swimming + if (cursor->GetProcedure() != "SWIM") return nil; + // For initial change, clonk should also be diving: Check for breath below 80% + // Use > instead of >= to ensure 0-breath-clonks can also get the environment + if (!is_current && cursor->GetBreath() > cursor.MaxBreath*4/5) return nil; + return this; +} + +private func EnvCheck_City(object cursor, int x, int y, bool is_current) +{ + // There must be buildings around the clonk + var building_count = cursor->ObjectCount(cursor->Find_AtRect(-180,-100,360,200), Find_Func("IsStructure")); + // 3 buildings to start the environment. Just 1 building to sustain it. + if (building_count < 3-2*is_current) return nil; + return this; +} + +private func EnvCheck_Lava(object cursor, int x, int y, bool is_current) +{ + // Check for lava pixels. First check if the last lava pixel we found is still in place. + var search_range; + if (is_current) + { + if (this.mat_mask[GetMaterial(this.last_x, this.last_y)+1]) + if (Distance(this.last_x, this.last_y, x, y) < 140) + return this; + search_range = 140; + } + else + { + search_range = 70; + } + // Now search for lava in search range + var ang = Random(360); + for (; search_range >= 0; search_range -= 10) + { + ang += 200; + var x2 = x + Sin(ang, search_range); + var y2 = y + Cos(ang, search_range); + if (this.mat_mask[GetMaterial(x2, y2)+1]) + { + // Lava found! + this.last_x = x2; + this.last_y = y2; + return this; + } + } + // No lava found + return nil; +} + +private func EnvCheck_Underground(object cursor, int x, int y, bool is_current) +{ + // Check for underground: No sky at cursor or above + if (GetMaterial(x,y)<0) return nil; + if (GetMaterial(x,y-30)<0) return nil; + if (GetMaterial(x-10,y-20)<0) return nil; + if (GetMaterial(x+10,y-20)<0) return nil; + return this; +} + +private func EnvCheck_Mountains(object cursor, int x, int y, bool is_current) +{ + // Check for mountains: Rock materials below + var num_rock; + for (var y2=0; y2<=45; y2+=15) + for (var x2=-75; x2<=75; x2+=15) + num_rock += this.mat_mask[GetMaterial(x+x2,y+y2)+1]; + // need 15pts on first check; 5 to sustain + if (num_rock < 15-is_current*10) return nil; + return this; +} + +private func EnvCheck_Snow(object cursor, int x, int y, bool is_current) +{ + // Must be snowing from above + if (GetPXSCount(this.mat, x-300, y-200, 600, 300) < 20 - is_current*15) return nil; + return this; +} + +private func EnvCheck_Night(object cursor, int x, int y, bool is_current) +{ + // Night time. + var time = FindObject(Find_ID(Environment_Time)); + if (!time || !time->IsNight()) return nil; + return this; +} + +private func EnvCheck_Overground(object cursor, int x, int y, bool is_current) +{ + // This is the fallback environment + return this; +} + +/*-- Proplist --*/ + +local Name = "$Name$"; +local Description = "$Description$"; +local Environment; diff --git a/planet/Experimental.ocd/Ambience.ocd/StringTblDE.txt b/planet/Experimental.ocd/Ambience.ocd/StringTblDE.txt new file mode 100644 index 000000000..e639e4a14 --- /dev/null +++ b/planet/Experimental.ocd/Ambience.ocd/StringTblDE.txt @@ -0,0 +1,2 @@ +Name=Ambiente +Description=Regelt die Geraeuschkulisse. diff --git a/planet/Experimental.ocd/Ambience.ocd/StringTblUS.txt b/planet/Experimental.ocd/Ambience.ocd/StringTblUS.txt new file mode 100644 index 000000000..21791f5e3 --- /dev/null +++ b/planet/Experimental.ocd/Ambience.ocd/StringTblUS.txt @@ -0,0 +1,3 @@ +Name=Ambience +Description=Controls environmental sounds and music. +