forked from Mirrors/openclonk
659 lines
19 KiB
C
659 lines
19 KiB
C
/**
|
|
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 player_environments; // array indexed by player number: array of derived environments with per-player data
|
|
local all_environments; // array of available environments for which it is checked if the player is in. sorted by priority.
|
|
|
|
local fixed_environment; // May be set to a string to fix the environment
|
|
local fixed_player_environments; // Per-player setting for environment
|
|
local EditorProps;
|
|
|
|
// Initialization
|
|
protected func Initialize()
|
|
{
|
|
// Initial player data
|
|
player_environments = [];
|
|
fixed_player_environments = [];
|
|
for (var i=0; i<GetPlayerCount(C4PT_User); ++i)
|
|
InitializePlayer(GetPlayerByIndex(i, C4PT_User));
|
|
// Periodic execution of ambience events
|
|
AddTimer(this.Execute, 10);
|
|
return true;
|
|
}
|
|
|
|
public func SetEnvironment(string new_env, int plr)
|
|
{
|
|
// Switch to a fixed environment
|
|
if (new_env && !GetLength(new_env)) new_env = nil;
|
|
// Update active flag(s)
|
|
var set_envs;
|
|
if (GetType(plr))
|
|
{
|
|
// Update for one player
|
|
fixed_player_environments[plr] = new_env;
|
|
set_envs = [player_environments[plr]];
|
|
}
|
|
else
|
|
{
|
|
// Update for all players
|
|
fixed_environment = new_env;
|
|
set_envs = player_environments;
|
|
fixed_player_environments = [];
|
|
}
|
|
for (var envs in set_envs)
|
|
{
|
|
if (envs)
|
|
{
|
|
for (var env in envs)
|
|
{
|
|
env.is_active = (env.Name == new_env);
|
|
env.change_delay = 999;
|
|
}
|
|
}
|
|
}
|
|
// Force the change now
|
|
for (var i = 0; i < 3; ++i)
|
|
{
|
|
Execute();
|
|
}
|
|
}
|
|
|
|
func InitializeEnvironments()
|
|
{
|
|
// Definition() call: Register all standard environments
|
|
all_environments = [];
|
|
|
|
// Underwater: Clonk is swimming in water
|
|
var underwater = this.env_underwater = new Environment
|
|
{
|
|
Name = "Underwater",
|
|
CheckPlayer = this.EnvCheck_Underwater,
|
|
music = "underwater",
|
|
sound_modifier = UnderwaterModifier,
|
|
exclusive = true, // Do not player other stuff (such as cave sound) when diving.
|
|
};
|
|
|
|
AddEnvironment(underwater, 1400);
|
|
|
|
// City: Clonk is surrounded by buildings
|
|
this.env_city = new Environment
|
|
{
|
|
Name = "City",
|
|
CheckPlayer = this.EnvCheck_City,
|
|
};
|
|
//AddEnvironment(this.env_city, 1200); - no music/sound for now
|
|
|
|
// Lava: Lava material is nearby
|
|
var lava = this.env_lava = new Environment
|
|
{
|
|
Name = "Lava",
|
|
CheckPlayer = this.EnvCheck_Lava,
|
|
min_change_delays = [1, 3], // Easy to miss lava on search.
|
|
};
|
|
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;
|
|
//AddEnvironment(lava, 1000); - no music/sound for now
|
|
|
|
// Underground: Clonk in front of tunnel
|
|
var underground = this.env_underground = new Environment
|
|
{
|
|
Name = "Underground",
|
|
CheckPlayer = this.EnvCheck_Underground,
|
|
music = "underground",
|
|
sound_modifier = CaveModifier,
|
|
};
|
|
AddEnvironment(underground, 800);
|
|
|
|
// Mountains: Overground and lots of rock around
|
|
var mountains = this.env_mountains = new Environment
|
|
{
|
|
Name = "Mountains",
|
|
CheckPlayer = this.EnvCheck_Mountains,
|
|
min_change_delay = [3, 3], // Pretty unstable condition
|
|
};
|
|
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;
|
|
//AddEnvironment(mountains, 600); - no music/sound for now
|
|
|
|
// Snow: It's snowing around the clonk
|
|
var snow = this.env_snow = new Environment
|
|
{
|
|
Name = "Snow",
|
|
CheckPlayer = this.EnvCheck_Snow,
|
|
min_change_delay = [1, 6], // Persist a while after snowing stopped
|
|
};
|
|
snow.mat = Material("Snow");
|
|
//AddEnvironment(snow, 400); - no music/sound for now
|
|
|
|
// Night: Sunlight blocked by planet
|
|
var night = this.env_night = new Environment
|
|
{
|
|
Name = "Night",
|
|
CheckPlayer = this.EnvCheck_Night,
|
|
music = "night",
|
|
};
|
|
AddEnvironment(night, 200);
|
|
|
|
// Day: Default environment
|
|
var day = this.env_day = new Environment
|
|
{
|
|
Name = "Day",
|
|
CheckPlayer = this.EnvCheck_Day,
|
|
music = "day",
|
|
};
|
|
AddEnvironment(day, 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
private func Execute()
|
|
{
|
|
// Per-player execution every third timer (~.8 seconds)
|
|
var i=GetPlayerCount(C4PT_User), plr;
|
|
exec_counter += !(i%3);
|
|
while (i--) if (!(++exec_counter % 3))
|
|
{
|
|
plr = GetPlayerByIndex(i, C4PT_User);
|
|
ExecutePlayer(plr, player_environments[plr]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private func ExecutePlayer(int plr, array environments)
|
|
{
|
|
var cursor = GetCursor(plr);
|
|
if (!cursor) cursor = GetPlrView(plr);
|
|
// Update active state of all player environments
|
|
if (cursor)
|
|
{
|
|
if (!(fixed_player_environments[plr] ?? fixed_environment))
|
|
{
|
|
var x = cursor->GetX(), y = cursor->GetY();
|
|
for (var env in environments)
|
|
{
|
|
var was_active = env.is_active;
|
|
var is_active = env->CheckPlayer(cursor, x, y, was_active);
|
|
if (is_active == was_active)
|
|
{
|
|
// No change. Reset change delays.
|
|
env.change_delay = 0;
|
|
}
|
|
else
|
|
{
|
|
// Environment change. The change must persist for a while to become active.
|
|
if (++env.change_delay > env.min_change_delays[!is_active])
|
|
{
|
|
// Waited long enough. Activate or deactivate this environment.
|
|
env.is_active = is_active;
|
|
//Log("%s environment: %s set to %v", GetPlayerName(plr), env.Name, is_active);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Sound and music by active environments
|
|
var has_music = false, sound_modifier = nil;
|
|
for (var env in environments) if (env.is_active)
|
|
{
|
|
// Music!
|
|
if (env.music && !has_music)
|
|
{
|
|
this->SetPlayList(env.music, plr, true, 3000, 10000);
|
|
has_music = true;
|
|
}
|
|
// Sound effects like cave reverb etc.
|
|
if (env.sound_modifier && !sound_modifier) sound_modifier = env.sound_modifier;
|
|
// Sounds and actions by environment
|
|
for (var action in env.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]);
|
|
}
|
|
}
|
|
// Does this stop all other environments?
|
|
if (env.is_exclusive) break;
|
|
}
|
|
// Apply or clear global sound modifier
|
|
SetGlobalSoundModifier(sound_modifier, plr);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
func InitializePlayer(int plr)
|
|
{
|
|
if (GetPlayerType(plr) == C4PT_User)
|
|
{
|
|
// Every player keeps a copy of the environment list to maintain delays
|
|
// Start with a large change delay to ensure first execution does set a proper environment
|
|
var n = GetLength(all_environments);
|
|
var envs = CreateArray(n);
|
|
for (var i=0; i < n; ++i)
|
|
envs[i] = new all_environments[i] { change_delay = 999, is_active = (all_environments[i].Name == fixed_environment) };
|
|
player_environments[plr] = envs;
|
|
// Newly joining players should have set playlist immediately (so they don't start playing a random song just to switch it immediately)
|
|
// However, this only works with a cursor
|
|
ExecutePlayer(plr, envs);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
func RemovePlayer(int plr)
|
|
{
|
|
// Ensure newly joining players don't check on another player's environment
|
|
player_environments[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.all_environments[GetLength(all_environments)] = new_env;
|
|
SortArrayByProperty(this.all_environments, "Priority", true);
|
|
return true;
|
|
}
|
|
|
|
private func Env_AddSound(chance, string snd_name)
|
|
{
|
|
return Env_AddAction(chance ?? 50, Global.Sound, snd_name);
|
|
}
|
|
|
|
private func Env_AddAction(achance, afn, par0, par1, par2, par3, par4)
|
|
{
|
|
// Make sure to not write into prototype proplist.
|
|
if (this.actions == this.Prototype.actions) this.actions = [];
|
|
var action = { chance=achance, fn=afn, par=[par0, par1, par2, par3, par4] };
|
|
this.actions[GetLength(this.actions)] = action;
|
|
return action;
|
|
}
|
|
|
|
/* 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 false;
|
|
// For initial change, clonk should also be diving: Check for breath below 95%
|
|
// Use > instead of >= to ensure 0-breath-clonks can also get the environment
|
|
if (!is_current && cursor->GetBreath() > cursor.MaxBreath*95/100) return false;
|
|
return true;
|
|
}
|
|
|
|
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 false;
|
|
return true;
|
|
}
|
|
|
|
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 true;
|
|
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 true;
|
|
}
|
|
}
|
|
// No lava found
|
|
return false;
|
|
}
|
|
|
|
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 false;
|
|
if (GetMaterial(x,y-30)<0) return false;
|
|
if (GetMaterial(x-10,y-20)<0) return false;
|
|
if (GetMaterial(x+10,y-20)<0) return false;
|
|
return true;
|
|
}
|
|
|
|
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 false;
|
|
return true;
|
|
}
|
|
|
|
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 false;
|
|
return true;
|
|
}
|
|
|
|
private func EnvCheck_Night(object cursor, int x, int y, bool is_current)
|
|
{
|
|
// Night time.
|
|
return Time->IsNight();
|
|
}
|
|
|
|
private func EnvCheck_Day(object cursor, int x, int y, bool is_current)
|
|
{
|
|
// This is the fallback environment
|
|
return true;
|
|
}
|
|
|
|
public func SaveScenarioObject(proplist props, ...)
|
|
{
|
|
// Only save ambience if it has modifications set for this scenario
|
|
return !!fixed_environment;
|
|
}
|
|
|
|
|
|
/*-- Proplist --*/
|
|
|
|
local SoundModifier, CaveModifier, UnderwaterModifier;
|
|
|
|
private func ReleaseSoundModifier() { return ChangeSoundModifier(this, true); }
|
|
private func UpdateSoundModifier() { return ChangeSoundModifier(this, false); } // OpenAL-Soft implementation does not work for all modifiers
|
|
|
|
public func IsAmbienceController() { return true; }
|
|
|
|
public func Definition(def)
|
|
{
|
|
// Base environment
|
|
def.Environment = {
|
|
actions = [],
|
|
min_change_delays = [1, 1],
|
|
AddSound = def.Env_AddSound,
|
|
AddAction = def.Env_AddAction,
|
|
};
|
|
// Base sound modifier
|
|
SoundModifier = {
|
|
Release = Ambience.ReleaseSoundModifier,
|
|
Update = Ambience.UpdateSoundModifier,
|
|
};
|
|
// Modifiers for different ambiences
|
|
CaveModifier = new SoundModifier {
|
|
Type = C4SMT_Reverb,
|
|
Reverb_Late_Reverb_Gain = 300,
|
|
Reverb_Late_Reverb_Delay = 10,
|
|
Reverb_Decay_HFRatio = 800,
|
|
};
|
|
UnderwaterModifier = nil; // not supported yet
|
|
// Register default environments
|
|
def->InitializeEnvironments();
|
|
// Initialize environment switching in editor
|
|
if (!def.EditorProps) def.EditorProps = {};
|
|
def.EditorProps.fixed_environment = { Name="$FixedEnv$", EditorHelp="$FixedEnvHelp$", Type="enum", Set="SetEnvironment", Options = [
|
|
{ Name="$Automatic$", EditorHelp="$AutomaticEnvHelp$" }
|
|
] };
|
|
var n = 0;
|
|
for (var env in all_environments)
|
|
{
|
|
EditorProps.fixed_environment.Options[++n] = { Name=env.Name, Value=env.Name };
|
|
}
|
|
// User actions
|
|
var env_options = [{ Name="$Automatic$", EditorHelp="$AutomaticEnvHelp$", Value="" }];
|
|
n = 0;
|
|
for (var env in all_environments)
|
|
{
|
|
env_options[++n] = { Name=env.Name, Value=env.Name };
|
|
}
|
|
UserAction->AddEvaluator("Action", "$Ambience$", "$SetEnvironment$", "$SetEnvironmentHelp$", "ambience_environment", [def, def.EvalAct_SetEnvironment], { Players={Function="all_players"}, Environment="" }, { Type="proplist", Display="{{Environment}} ({{Players}})", EditorProps = {
|
|
Environment = { Name="$Environment$", Type="enum", Editable=true, Options = env_options },
|
|
Players = UserAction.Evaluator.PlayerList
|
|
} } );
|
|
// Shader
|
|
UserAction->AddEvaluator("Action", "$Ambience$", "$SetShader$", "$SetShaderHelp$", "ambience_shader", [def, def.EvalAct_SetShader], { ShaderName="Grayscale", Status = { Function="bool_constant", Value=true } }, { Type="proplist", Display="{{ShaderName}} ({{Status}})", EditorProps = {
|
|
ShaderName = { Name="$Shader$", Type="enum", Options = [
|
|
{ Name="$None$" },
|
|
{ Name="$Grayscale$", Value="Grayscale" },
|
|
{ Name="$Psycho$", Value="Psycho" }
|
|
] },
|
|
Status = new UserAction.Evaluator.Boolean { Name = "$Status$" }
|
|
} } );
|
|
return true;
|
|
}
|
|
|
|
local Environment;
|
|
|
|
local Name = "$Name$";
|
|
local Description = "$Description$";
|
|
|
|
|
|
|
|
|
|
/**
|
|
Ambience Objects
|
|
Cares about the placement of purely visual objects and handles sound ambience
|
|
The placement uses categories and thus is forward-compatible.
|
|
*/
|
|
|
|
// this proplist defines the selectable environment objects
|
|
// "ID" might be nil or a valid id
|
|
// "includes" specifies what objects are created when the selected object is created (no specific order)
|
|
// any entry of "Environment_Attributes" might be nil or false instead of a proplist
|
|
// nil will log a warning on creating that object and false will silently ignore it
|
|
// thus something like Environment_Attributes["Foobar"] = false; will work to disable certain features
|
|
static const Environment_Attributes =
|
|
{
|
|
All = {
|
|
ID = nil,
|
|
includes = ["Temperate", "Desert"],
|
|
},
|
|
|
|
Temperate = {
|
|
ID = nil,
|
|
includes = ["Zicadas", "Frogs", "BackgroundBirds"],
|
|
},
|
|
|
|
Desert = {
|
|
ID = nil,
|
|
includes = ["Zicadas"],
|
|
},
|
|
|
|
Zicadas = {
|
|
ID = Ambience_Zicadas,
|
|
},
|
|
|
|
Frogs = {
|
|
ID = nil /* not yet implemented: Environment_Frogs */,
|
|
},
|
|
|
|
BackgroundBirds = {
|
|
ID = nil /* not yet implemented: Environment_BackgroundBirds */,
|
|
},
|
|
};
|
|
|
|
|
|
// provides a simple interface for creation of environment objects and decoration with standard values
|
|
// the objects are placed on a best-effort-basis. That means f.e. objects that rely on water will not be placed when there is no water in the landscape.
|
|
global func CreateEnvironmentObjects(
|
|
what /* array of strings or single string: what objects will be created, standard: "All" */
|
|
, proplist area /* area where objects will be created, format {x = ??, y = ??, w = ??, h = ??}, standard: whole landscape */
|
|
, int amount_percentage /* what percentage of the standard amount will be created, standard: 100 */
|
|
)
|
|
{
|
|
/*
|
|
// half desert, half temperate - but birds everywhere
|
|
CreateEnvironmentObjects(["Desert", "BackgroundBirds"], Rectangle(0, 0, LandscapeWidth()/2, LandscapeHeight()));
|
|
CreateEnvironmentObjects("Temperate", Rectangle(LandscapeWidth()/2, 0, LandscapeWidth()/2, LandscapeHeight()));
|
|
*/
|
|
what = what ?? "All";
|
|
area = area ?? Shape->LandscapeRectangle();
|
|
amount_percentage = amount_percentage ?? 100;
|
|
|
|
// might be a string to allow CreateEnvironmentObjects("All")
|
|
if(GetType(what) != C4V_Array)
|
|
what = [what];
|
|
|
|
// iteratively find all the objects that are included in the selection
|
|
while(true)
|
|
{
|
|
var changed = false;
|
|
var to_add = [];
|
|
|
|
// go through every object in the list
|
|
for(var obj in what)
|
|
{
|
|
var p = Environment_Attributes[obj];
|
|
if(p == nil) {Log("Warning: Environment object %s does not exist!", obj);}
|
|
else if(p == false) continue; // disabled by the scenario designer
|
|
|
|
// add all objects included to the temporary list if existing
|
|
if(!p["includes"]) continue;
|
|
to_add = Concatenate(to_add, p["includes"]);
|
|
}
|
|
|
|
// add every unique item from the temporary list to the object list
|
|
for(var obj in to_add)
|
|
{
|
|
if(IsValueInArray(what, obj)) continue;
|
|
|
|
if(!!Environment_Attributes[obj]["includes"])
|
|
changed = true; // found changes, need further checking
|
|
|
|
PushBack(what, obj);
|
|
}
|
|
|
|
if(!changed)
|
|
break;
|
|
}
|
|
|
|
// now create all the selected objects
|
|
for(var obj in what)
|
|
{
|
|
var p, p_id;
|
|
if(!(p = Environment_Attributes[obj])) continue;
|
|
if(!(p_id = p["ID"])) continue;
|
|
|
|
p_id->Place(amount_percentage, area);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Global sound ambience object creation */
|
|
|
|
global func InitializeAmbience()
|
|
{
|
|
// Fallback for when this call is not defined in scenario: Ensure there is an ambience controller object
|
|
if (!FindObject(Find_Func("IsAmbienceController")))
|
|
{
|
|
CreateObject(Ambience);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/* Shaders */
|
|
|
|
local active_shaders;
|
|
|
|
local CommonShaders = {
|
|
Grayscale = ["Common", "slice(finish+20) { fragColor = vec4(vec3(0.299*fragColor.r + 0.587*fragColor.g + 0.114*fragColor.b), fragColor.a); }"],
|
|
Psycho = ["Landscape", "slice(color+1) { fragColor = scalerPx.r < 1 ? materialPx : vec4(vec3(0), materialPx.a); }"],
|
|
};
|
|
|
|
public func SetShaderStatus(shader_name, bool to_active)
|
|
{
|
|
// Foreward to instance
|
|
if (this == Ambience)
|
|
{
|
|
var ambience = FindObject(Find_ID(Ambience));
|
|
if (ambience) return ambience->SetShaderStatus(shader_name, to_active);
|
|
return false;
|
|
}
|
|
var shader_info = CommonShaders[shader_name];
|
|
if (!shader_info) return false;
|
|
if (!active_shaders) active_shaders = {};
|
|
var shader_index = active_shaders[shader_name];
|
|
var is_active = !!GetType(shader_index);
|
|
if (is_active != to_active)
|
|
{
|
|
if (to_active)
|
|
{
|
|
active_shaders[shader_name] = AddFragmentShader(shader_info[0], shader_info[1]);
|
|
}
|
|
else
|
|
{
|
|
RemoveShader(shader_index);
|
|
active_shaders[shader_name] = nil;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public func Destruction()
|
|
{
|
|
// Remove active shaders.
|
|
if (active_shaders)
|
|
{
|
|
var shaders = GetProperties(active_shaders);
|
|
for (var shader in shaders)
|
|
RemoveShader(active_shaders[shader]);
|
|
}
|
|
return _inherited(...);
|
|
}
|
|
|
|
private func EvalAct_SetShader(proplist props, proplist context)
|
|
{
|
|
var shader_name = props.ShaderName;
|
|
var status = UserAction->EvaluateValue("Boolean", props.Status, context);
|
|
return SetShaderStatus(shader_name, status);
|
|
}
|
|
|
|
private func EvalAct_SetEnvironment(proplist props, proplist context)
|
|
{
|
|
var new_environment = props.Environment;
|
|
var is_all_players = (props.Players.Function == "all_players"); // special handling for "all players"
|
|
var ambience = FindObject(Find_ID(Ambience));
|
|
if (!ambience) return;
|
|
if (is_all_players)
|
|
{
|
|
ambience->SetEnvironment(new_environment);
|
|
}
|
|
else
|
|
{
|
|
var players = UserAction->EvaluateValue("PlayerList", props.Players, context) ?? [];
|
|
for (var plr in players) ambience->SetEnvironment(new_environment, plr);
|
|
}
|
|
}
|