Files
2018-07-17 14:15:37 +02:00

461 lines
12 KiB
ActionScript

#priority init 2000
import hooks;
import saving;
import planet_types;
import resources;
export StatusType;
export Status;
export StatusInstance;
export getStatusType, getStatusTypeCount;
export getStatusID;
export getRandomCondition;
export StatusVisibility;
enum StatusVisibility {
StV_Everybody,
StV_Owner,
StV_Origin,
StV_OwnerAndOrigin,
StV_Global,
StV_Nobody,
};
tidy final class StatusType {
uint id;
string ident;
string name;
string description;
Sprite icon;
Color color;
StatusVisibility visibility = StV_Everybody;
bool unique = false;
bool collapses = false;
array<IStatusHook@> hooks;
string def_conditionType;
const PlanetType@ conditionType;
uint conditionTier = 0;
double conditionFrequency = 0.0;
bool shouldApply(Empire@ emp, Region@ region, Object@ obj) const {
if(!obj.hasStatuses)
return false;
for(uint i = 0, cnt = hooks.length; i < cnt; ++i) {
if(!hooks[i].shouldApply(emp, region, obj))
return false;
}
return true;
}
};
interface IStatusHook {
void onCreate(Object& obj, Status@ status, any@ data);
void onDestroy(Object& obj, Status@ status, any@ data);
void onObjectDestroy(Object& obj, Status@ status, any@ data);
bool onTick(Object& obj, Status@ status, any@ data, double time);
void onAddStack(Object& obj, Status@ status, StatusInstance@ instance, any@ data);
void onRemoveStack(Object& obj, Status@ status, StatusInstance@ instance, any@ data);
bool onOwnerChange(Object& obj, Status@ status, any@ data, Empire@ prevOwner, Empire@ newOwner);
bool onRegionChange(Object& obj, Status@ status, any@ data, Region@ prevRegion, Region@ newRegion);
void save(Status@ status, any@ data, SaveFile& file);
void load(Status@ status, any@ data, SaveFile& file);
bool shouldApply(Empire@ emp, Region@ region, Object@ obj) const;
bool getVariable(Object@ obj, Sprite& sprt, string& name, string& value, Color& color) const;
};
class StatusHook : Hook, IStatusHook {
void onCreate(Object& obj, Status@ status, any@ data) {}
void onDestroy(Object& obj, Status@ status, any@ data) {}
void onObjectDestroy(Object& obj, Status@ status, any@ data) {}
bool onTick(Object& obj, Status@ status, any@ data, double time) { return true; }
void onAddStack(Object& obj, Status@ status, StatusInstance@ instance, any@ data) {}
void onRemoveStack(Object& obj, Status@ status, StatusInstance@ instance, any@ data) {}
bool onOwnerChange(Object& obj, Status@ status, any@ data, Empire@ prevOwner, Empire@ newOwner) { return true; }
bool onRegionChange(Object& obj, Status@ status, any@ data, Region@ prevRegion, Region@ newRegion) { return true; }
void save(Status@ status, any@ data, SaveFile& file) {}
void load(Status@ status, any@ data, SaveFile& file) {}
bool shouldApply(Empire@ emp, Region@ region, Object@ obj) const { return true; }
bool getVariable(Object@ obj, Sprite& sprt, string& name, string& value, Color& color) const { return false; }
};
tidy final class Status : Serializable, Savable {
const StatusType@ type;
int stacks = 0;
any[] data;
Empire@ originEmpire;
Object@ originObject;
Status() {
}
Status(const StatusType@ type) {
set(type);
}
string getTooltip(Object@ valueObject = null) const {
string tt;
string name = type.name;
if(!type.unique && stacks > 1)
name += format(" (x $1)", toString(stacks));
tt = format("[font=Medium][color=$3]$1[/color][/font]\n$2",
name, type.description, toString(type.color));
string vname, vvalue;
Sprite vicon;
Color color;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(type.hooks[i].getVariable(valueObject, vicon, vname, vvalue, color)) {
tt += format("[nl/]\n[img=$1;22][b][color=$4]$2[/color][/b] [offset=120]$3[/offset][/img]",
getSpriteDesc(vicon), vname, vvalue, toString(color));
color = colors::White;
}
}
return tt;
}
bool isVisibleTo(Object& obj, Empire@ emp) const {
if(emp is null || !emp.valid)
return type.visibility == StV_Everybody;
switch(type.visibility) {
case StV_Everybody:
case StV_Global:
return true;
case StV_Owner:
return emp is obj.owner;
case StV_Origin:
return emp is originEmpire;
case StV_OwnerAndOrigin:
return emp is originEmpire || emp is obj.owner;
case StV_Nobody:
return false;
}
return false;
}
void set(const StatusType@ type) {
@this.type = type;
stacks = 0;
data.length = type.hooks.length;
}
void write(Message& msg) {
msg.writeSmall(type.id);
msg.writeSmall(stacks);
msg << originEmpire;
msg << originObject;
}
void read(Message& msg) {
@type = getStatusType(msg.readSmall());
stacks = msg.readSmall();
data.length = type.hooks.length;
msg >> originEmpire;
msg >> originObject;
}
void save(SaveFile& file) {
file.writeIdentifier(SI_Status, type.id);
file << stacks;
file << originEmpire;
file << originObject;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].save(this, data[i], file);
}
void load(SaveFile& file) {
@type = getStatusType(file.readIdentifier(SI_Status));
file >> stacks;
if(file >= SV_0083) {
file >> originEmpire;
file >> originObject;
}
data.length = type.hooks.length;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].load(this, data[i], file);
}
void create(Object& obj) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onCreate(obj, this, data[i]);
}
void destroy(Object& obj) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onDestroy(obj, this, data[i]);
}
void objectDestroy(Object& obj) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onObjectDestroy(obj, this, data[i]);
}
StatusInstance@ instance(Object& obj) {
StatusInstance inst;
@inst.status = this;
stacks += 1;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onAddStack(obj, this, inst, data[i]);
return inst;
}
void remove(Object& obj, StatusInstance@ inst) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onRemoveStack(obj, this, inst, data[i]);
stacks -= 1;
}
bool tick(Object& obj, double time) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(!type.hooks[i].onTick(obj, this, data[i], time))
return false;
}
return true;
}
bool ownerChange(Object& obj, Empire@ prevOwner, Empire@ newOwner) {
bool keep = true;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(!type.hooks[i].onOwnerChange(obj, this, data[i], prevOwner, newOwner))
keep = false;
}
return keep;
}
bool regionChange(Object& obj, Region@ prevRegion, Region@ newRegion) {
bool keep = true;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(!type.hooks[i].onRegionChange(obj, this, data[i], prevRegion, newRegion))
keep = false;
}
return keep;
}
};
tidy final class StatusInstance : Savable {
Status@ status;
int id = -1;
double timer = -1.0;
Region@ boundRegion;
Empire@ boundEmpire;
void remove(Object& obj) {
status.remove(obj, this);
}
bool tick(Object& obj, double time) {
if(timer >= 0) {
timer -= time;
if(timer <= 0) {
status.remove(obj, this);
return false;
}
}
return true;
}
void save(SaveFile& file) {
file << id;
file << timer;
file << boundRegion;
file << boundEmpire;
}
void load(SaveFile& file) {
file >> id;
file >> timer;
file >> boundRegion;
file >> boundEmpire;
}
};
array<StatusType@> statusTypes;
dictionary statusIdents;
int getStatusID(const string& ident) {
auto@ type = getStatusType(ident);
if(type is null)
return -1;
return int(type.id);
}
string getStatusIdent(int id) {
auto@ type = getStatusType(id);
if(type is null)
return "";
return type.ident;
}
const StatusType@ getStatusType(uint id) {
if(id >= statusTypes.length)
return null;
return statusTypes[id];
}
const StatusType@ getStatusType(const string& ident) {
StatusType@ def;
if(statusIdents.get(ident, @def))
return def;
return null;
}
uint getStatusTypeCount() {
return statusTypes.length;
}
const StatusType@ getRandomCondition(Planet& planet) {
double roll = randomd();
double freq = 0.0;
const StatusType@ result;
for(uint i = 0, cnt = statusTypes.length; i < cnt; ++i) {
auto@ status = statusTypes[i];
if(status.conditionFrequency <= 0)
continue;
if(status.conditionType !is null) {
if(planet.PlanetType != status.conditionType.id)
continue;
}
if(status.conditionTier != 0) {
auto@ type = getResource(planet.primaryResourceType);
if(type is null || type.level < status.conditionTier)
continue;
}
if(!status.shouldApply(planet.owner, planet.region, planet))
continue;
freq += status.conditionFrequency;
double chance = status.conditionFrequency / freq;
if(roll < chance) {
@result = status;
roll /= chance;
}
else {
roll = (roll - chance) / (1.0 - chance);
}
}
return result;
}
void saveIdentifiers(SaveFile& file) {
for(uint i = 0, cnt = statusTypes.length; i < cnt; ++i) {
auto type = statusTypes[i];
file.addIdentifier(SI_Status, type.id, type.ident);
}
}
void addStatus(StatusType@ type) {
type.id = statusTypes.length;
statusTypes.insertLast(type);
statusIdents.set(type.ident, @type);
}
void parseLine(string& line, StatusType@ type, ReadFile@ file) {
auto@ hook = cast<IStatusHook>(parseHook(line, "status_effects::", instantiate=false, file=file));
if(hook !is null)
type.hooks.insertLast(hook);
}
void loadStatus(const string& filename) {
ReadFile file(filename, true);
string key, value;
StatusType@ status;
uint index = 0;
while(file++) {
key = file.key;
value = file.value;
if(file.fullLine) {
string line = file.line;
parseLine(line, status, file);
}
else if(key.equals_nocase("Status")) {
if(status !is null)
addStatus(status);
@status = StatusType();
status.ident = value;
}
else if(status is null) {
file.error("Missing status ID' line");
}
else if(key.equals_nocase("Name")) {
status.name = localize(value);
}
else if(key.equals_nocase("Description")) {
status.description = localize(value);
}
else if(key.equals_nocase("Icon")) {
status.icon = getSprite(value);
}
else if(key.equals_nocase("Color")) {
status.color = toColor(value);
}
else if(key.equals_nocase("Condition Frequency")) {
status.conditionFrequency = toDouble(value);
}
else if(key.equals_nocase("Condition Tier")) {
status.conditionTier = toUInt(value);
}
else if(key.equals_nocase("Condition Type")) {
status.def_conditionType = value;
}
else if(key.equals_nocase("Unique")) {
status.unique = toBool(value);
if(status.unique)
status.collapses = true;
}
else if(key.equals_nocase("Collapses")) {
status.collapses = toBool(value);
}
else if(key.equals_nocase("Visible To")) {
if(value.equals_nocase("everybody"))
status.visibility = StV_Everybody;
else if(value.equals_nocase("owner"))
status.visibility = StV_Owner;
else if(value.equals_nocase("origin empire"))
status.visibility = StV_Origin;
else if(value.equals_nocase("owner and origin empire"))
status.visibility = StV_OwnerAndOrigin;
else if(value.equals_nocase("nobody"))
status.visibility = StV_Nobody;
else if(value.equals_nocase("global"))
status.visibility = StV_Global;
else
file.error("Unknown status visibility: "+value);
}
else {
string line = file.line;
parseLine(line, status, file);
}
}
if(status !is null)
addStatus(status);
}
void preInit() {
FileList list("data/statuses", "*.txt", true);
for(uint i = 0, cnt = list.length; i < cnt; ++i)
loadStatus(list.path[i]);
}
void init() {
auto@ list = statusTypes;
for(uint i = 0, cnt = list.length; i < cnt; ++i) {
auto@ type = list[i];
for(uint n = 0, ncnt = type.hooks.length; n < ncnt; ++n)
if(!cast<Hook>(type.hooks[n]).instantiate())
error("Could not instantiate hook: "+addrstr(type.hooks[n])+" in "+type.ident);
if(type.def_conditionType.length != 0) {
@type.conditionType = getPlanetType(type.def_conditionType);
if(type.conditionType is null)
error("Error in Status "+type.ident+": could not find planet type "+type.def_conditionType);
}
}
}