Files
starruler-linux/scripts/definitions/trait_effects.as
T
2018-07-17 14:15:37 +02:00

710 lines
19 KiB
ActionScript

import traits;
import influence;
from traits import TraitEffect;
from bonus_effects import BonusEffect;
from planet_effects import GenericEffect;
from map_effects import MapHook;
import bonus_effects;
import research;
import hooks;
import statuses;
import empire_effects;
import util.design_export;
#section server
import object_creation;
from empire import Creeps;
import systems;
import regions.regions;
#section all
class HWData {
Empire@ prevOwner;
Region@ prevRegion;
any data;
};
tidy final class OnHomeworld : TraitEffect {
GenericEffect@ hook;
BonusEffect@ bonus;
Document doc("Apply the specified continuous effect on the homeworld.");
Argument hookID("Hook", AT_Hook, "planet_effects::GenericEffect");
bool instantiate() override {
@hook = cast<GenericEffect>(parseHook(arguments[0].str, "planet_effects::", required=false));
if(hook is null) {
@bonus = cast<BonusEffect>(parseHook(arguments[0].str, "bonus_effects::"));
if(bonus is null)
return false;
}
return TraitEffect::instantiate();
}
void init(Empire& emp, any@ data) const override {
if(hook !is null) {
HWData dat;
data.store(@dat);
hook.enable(emp.Homeworld, dat.data);
@dat.prevOwner = emp.Homeworld.owner;
@dat.prevRegion = emp.Homeworld.region;
}
else if(bonus !is null) {
bonus.activate(emp.Homeworld, emp);
}
}
void tick(Empire& emp, any@ data, double time) const override {
if(hook is null)
return;
HWData@ dat;
data.retrieve(@dat);
Object@ hw = emp.Homeworld;
if(hw is null)
return;
Empire@ owner = hw.owner;
if(owner !is dat.prevOwner) {
hook.ownerChange(hw, dat.data, dat.prevOwner, owner);
@dat.prevOwner = owner;
}
Region@ region = hw.region;
if(region !is dat.prevRegion) {
hook.regionChange(hw, dat.data, dat.prevRegion, region);
@dat.prevRegion = region;
}
hook.tick(hw, dat.data, time);
}
void save(any@ data, SaveFile& file) const override {
if(hook is null)
return;
HWData@ dat;
data.retrieve(@dat);
file << dat.prevOwner;
file << dat.prevRegion;
hook.save(dat.data, file);
}
void load(any@ data, SaveFile& file) const override {
if(hook is null)
return;
HWData dat;
data.store(@dat);
file >> dat.prevOwner;
file >> dat.prevRegion;
hook.load(dat.data, file);
}
};
tidy final class TriggerHomeworld : TraitEffect {
BonusEffect@ bonus;
Document doc("Apply the specified one-time trigger effect on the homeworld.");
Argument hookID("Hook", AT_Hook, "bonus_effects::BonusEffect");
Argument run_post(AT_Boolean, "False", doc="Whether to run the trigger as a post init event.");
bool instantiate() override {
@bonus = cast<BonusEffect>(parseHook(arguments[0].str, "bonus_effects::"));
if(bonus is null)
return false;
return TraitEffect::instantiate();
}
void init(Empire& emp, any@ data) const override {
if(!run_post.boolean) {
if(emp.Homeworld !is null && emp.Homeworld.valid)
bonus.activate(emp.Homeworld, emp);
else if(emp.HomeObj !is null)
bonus.activate(emp.HomeObj, emp);
}
}
void postInit(Empire& emp, any@ data) const override {
if(run_post.boolean) {
if(emp.Homeworld !is null && emp.Homeworld.valid)
bonus.activate(emp.Homeworld, emp);
else if(emp.HomeObj !is null)
bonus.activate(emp.HomeObj, emp);
}
}
};
tidy final class InHomeSystem : TraitEffect {
MapHook@ hook;
Document doc("Apply the specified map generation effect on the home system.");
Argument hookID("Hook", AT_Hook, "map_effects::IMapHook");
bool instantiate() override {
@hook = cast<MapHook>(parseHook(arguments[0].str, "map_effects::"));
if(hook is null)
return false;
return TraitEffect::instantiate();
}
#section server
void init(Empire& emp, any@ data) const override {
Region@ region = emp.HomeSystem;
if(region is null && emp.Homeworld !is null)
@region = getRegion(emp.Homeworld.position);
if(region is null && emp.HomeObj !is null)
@region = getRegion(emp.HomeObj.position);
if(region is null)
return;
SystemDesc@ sys = getSystem(region);
if(sys is null)
return;
Object@ current;
hook.trigger(null, sys, current);
}
#section all
};
tidy final class TriggerHomeSystem : TraitEffect {
BonusEffect@ bonus;
Document doc("Apply the specified one-time trigger effect on the home system.");
Argument hookID("Hook", AT_Hook, "bonus_effects::BonusEffect");
Argument run_post(AT_Boolean, "False", doc="Whether to run the trigger as a post init event.");
bool instantiate() override {
@bonus = cast<BonusEffect>(parseHook(arguments[0].str, "bonus_effects::"));
if(bonus is null)
return false;
return TraitEffect::instantiate();
}
#section server
void init(Empire& emp, any@ data) const override {
if(!run_post.boolean) {
Region@ region = emp.HomeSystem;
if(region is null && emp.Homeworld !is null)
@region = getRegion(emp.Homeworld.position);
if(region is null && emp.HomeObj !is null)
@region = getRegion(emp.HomeObj.position);
if(region !is null)
bonus.activate(region, emp);
}
}
void postInit(Empire& emp, any@ data) const override {
if(run_post.boolean) {
Region@ region = emp.HomeSystem;
if(region is null && emp.Homeworld !is null)
@region = getRegion(emp.Homeworld.position);
if(region is null && emp.HomeObj !is null)
@region = getRegion(emp.HomeObj.position);
if(region !is null)
bonus.activate(region, emp);
}
}
#section all
};
tidy final class InRandomAdjacentSystem : TraitEffect {
MapHook@ hook;
Document doc("Apply the specified map generation effect on a random system adjacent to the home system.");
Argument hookID("Hook", AT_Hook, "map_effects::IMapHook");
bool instantiate() override {
@hook = cast<MapHook>(parseHook(arguments[0].str, "map_effects::"));
if(hook is null)
return false;
return TraitEffect::instantiate();
}
#section server
void init(Empire& emp, any@ data) const override {
Region@ region = emp.HomeSystem;
if(region is null && emp.Homeworld !is null)
@region = getRegion(emp.Homeworld.position);
if(region is null && emp.HomeObj !is null)
@region = getRegion(emp.HomeObj.position);
if(region is null)
return;
SystemDesc@ sys = getSystem(region);
if(sys is null)
return;
if(sys.adjacent.length == 0)
return;
SystemDesc@ adj = getSystem(sys.adjacent[randomi(0, sys.adjacent.length-1)]);
if(adj is null)
return;
Object@ current;
hook.trigger(null, adj, current);
}
#section all
};
tidy final class InAllAdjacentSystems : TraitEffect {
MapHook@ hook;
Document doc("Apply the specified map generation effect on all systems adjacent to the home system.");
Argument hookID("Hook", AT_Hook, "map_effects::IMapHook");
bool instantiate() override {
@hook = cast<MapHook>(parseHook(arguments[0].str, "map_effects::"));
if(hook is null)
return false;
return TraitEffect::instantiate();
}
#section server
void init(Empire& emp, any@ data) const override {
Region@ region = emp.HomeSystem;
if(region is null && emp.Homeworld !is null)
@region = getRegion(emp.Homeworld.position);
if(region is null && emp.HomeObj !is null)
@region = getRegion(emp.HomeObj.position);
if(region is null)
return;
SystemDesc@ sys = getSystem(region);
if(sys is null)
return;
for(uint i = 0, cnt = sys.adjacent.length; i < cnt; ++i) {
auto@ other = getSystem(sys.adjacent[i]);
if(other is null)
continue;
Object@ current;
hook.trigger(null, other, current);
}
}
#section all
};
class RemoveAllCards : TraitEffect {
Document doc("Remove all influence cards the empire starts with.");
#section server
void postInit(Empire& emp, any@ data) const override {
array<InfluenceCard> cards;
cards.syncFrom(emp.getInfluenceCards());
for(uint i = 0, cnt = cards.length; i < cnt; ++i)
emp.takeCardUse(cards[i].id, uint(-1));
}
#section all
};
class LoadDesigns : TraitEffect {
Document doc("Load default designs from a particular directory into the empire.");
Argument directory(AT_Custom, doc="Relative path to the directory to add designs from.");
Argument limit_shipset(AT_Boolean, "True", doc="Whether to only load designs that have a saved hull that matches the current shipset.");
Argument retry_without_limit(AT_Boolean, "True", doc="Whether to override the shipset limit and load all designs if no designs were found.");
#section server
DesignSet designs;
bool instantiate() override {
designs.readDirectory("data/designs/"+directory.str);
designs.limitShipset = limit_shipset.boolean;
designs.softLimitRetry = retry_without_limit.boolean;
return TraitEffect::instantiate();
}
void init(Empire& emp, any@ data) const override {
designs.createFor(emp);
}
#section all
};
class PlanetReqData {
double timer;
set_int set;
array<Planet@> list;
array<int> ids;
};
class AddStatusPlanetsReqLevel : TraitEffect {
Document doc("Add a status effect to all planets of a particular level or higher.");
Argument level(AT_Integer, doc="Minimum level of planets that get the status.");
Argument status(AT_Status, doc="Type of status to add to planets.");
#section server
void init(Empire& emp, any@ data) const override {
PlanetReqData list;
data.store(@list);
}
void tick(Empire& emp, any@ data, double time) const override {
PlanetReqData@ list;
data.retrieve(@list);
if(list is null)
return;
list.timer -= time;
if(list.timer >= 0)
return;
uint minLevel = arguments[0].integer;
list.timer = 5.0;
//Check old
for(int i = list.list.length - 1; i >= 0; --i) {
Planet@ pl = list.list[i];
if(pl.level < minLevel || pl.owner !is emp) {
pl.removeStatus(list.ids[i]);
list.list.removeAt(uint(i));
list.ids.removeAt(uint(i));
list.set.erase(pl.id);
}
}
//Check new
DataList@ objs = emp.getPlanets();
Object@ obj;
while(receive(objs, obj)) {
Planet@ pl = cast<Planet>(obj);
if(!list.set.contains(pl.id) && pl.level >= minLevel) {
int id = pl.addStatus(-1.0, arguments[1].integer);
list.list.insertLast(pl);
list.ids.insertLast(id);
list.set.insert(pl.id);
}
}
}
void save(any@ data, SaveFile& file) const override {
PlanetReqData@ list;
data.retrieve(@list);
if(list is null) {
file.write0();
return;
}
file.write1();
file << list.timer;
file << list.list.length;
for(uint i = 0, cnt = list.list.length; i < cnt; ++i) {
file << list.list[i];
file << list.ids[i];
}
}
void load(any@ data, SaveFile& file) const override {
if(file >= SV_0043 && file.readBit()) {
PlanetReqData list;
data.store(@list);
file >> list.timer;
uint cnt = 0;
file >> cnt;
list.list.length = cnt;
list.ids.length = cnt;
for(uint i = 0; i < cnt; ++i) {
file >> list.list[i];
file >> list.ids[i];
list.set.insert(list.list[i].id);
}
}
}
#section all
};
class SetColonizerInfo : TraitEffect {
Document doc("Change the properties of what this race's colonizers look like.");
Argument name(AT_Locale, "#COLONY_SHIP", doc="Name of a colonizer.");
Argument model(AT_Custom, EMPTY_DEFAULT, doc="Model to use for the colonizer.");
Argument material(AT_Custom, EMPTY_DEFAULT, doc="Material to use for the colonizer.");
void init(Empire& emp, any@ data) const override {
emp.ColonizerName = name.str;
if(model.str.length != 0) {
emp.ColonizerModel = model.str;
emp.ColonizerMaterial = material.str;
}
}
};
tidy final class IfHaveTrait : TraitEffect {
Document doc("Only apply the inner hook if the empire also has another trait. Cannot be used for continuous effects, only ones applied once at game start.");
Argument trait(AT_Trait, doc="Trait to check for.");
Argument hookID("Hook", AT_Hook, "trait_effects::TraitEffect");
Argument elseID("Else", AT_Hook, "", "trait_effects::TraitEffect");
ITraitEffect@ hook;
ITraitEffect@ elseHook;
bool instantiate() override {
@hook = cast<ITraitEffect>(parseHook(hookID.str, "trait_effects::"));
if(elseID.str.length != 0)
@elseHook = cast<ITraitEffect>(parseHook(elseID.str, "trait_effects::"));
return TraitEffect::instantiate();
}
void preInit(Empire& emp, any@ data) const {
if(emp.hasTrait(trait.integer)) {
if(hook !is null)
hook.preInit(emp, data);
}
else {
if(elseHook !is null)
elseHook.preInit(emp, data);
}
}
void init(Empire& emp, any@ data) const {
if(emp.hasTrait(trait.integer)) {
if(hook !is null)
hook.init(emp, data);
}
else {
if(elseHook !is null)
elseHook.init(emp, data);
}
}
void postInit(Empire& emp, any@ data) const {
if(emp.hasTrait(trait.integer)) {
if(hook !is null)
hook.postInit(emp, data);
}
else {
if(elseHook !is null)
elseHook.postInit(emp, data);
}
}
};
tidy final class IfNotHaveTrait : TraitEffect {
Document doc("Only apply the inner hook if the empire does not have another trait. Cannot be used for continuous effects, only ones applied once at game start.");
Argument trait(AT_Trait, doc="Trait to check for.");
Argument hookID("Hook", AT_Hook, "trait_effects::TraitEffect");
Argument elseID("Else", AT_Hook, "", "trait_effects::TraitEffect");
ITraitEffect@ hook;
ITraitEffect@ elseHook;
bool instantiate() override {
@hook = cast<ITraitEffect>(parseHook(hookID.str, "trait_effects::"));
if(elseID.str.length != 0)
@elseHook = cast<ITraitEffect>(parseHook(elseID.str, "trait_effects::"));
return TraitEffect::instantiate();
}
void preInit(Empire& emp, any@ data) const {
if(!emp.hasTrait(trait.integer)) {
if(hook !is null)
hook.preInit(emp, data);
}
else {
if(elseHook !is null)
elseHook.preInit(emp, data);
}
}
void init(Empire& emp, any@ data) const {
if(!emp.hasTrait(trait.integer)) {
if(hook !is null)
hook.init(emp, data);
}
else {
if(elseHook !is null)
elseHook.init(emp, data);
}
}
void postInit(Empire& emp, any@ data) const {
if(!emp.hasTrait(trait.integer)) {
if(hook !is null)
hook.postInit(emp, data);
}
else {
if(elseHook !is null)
elseHook.postInit(emp, data);
}
}
};
class AddRandomTrait : TraitEffect {
Document doc("Add a random trait with a particular unique tag to the empire.");
Argument unique_type(AT_Custom, doc="'Unique' tag to select a trait from.");
Argument ignore_trait(AT_Trait, EMPTY_DEFAULT, doc="A trait to ignore in the randomization.");
Argument only_available(AT_Boolean, "True", doc="Whether to only choose from traits that are normally available to be chosen.");
#section server
void preInit(Empire& emp, any@ data) const {
const Trait@ ourType;
const Trait@ type;
double total = 0;
const ITraitEffect@ cmp = this;
for(uint i = 0, cnt = getTraitCount(); i < cnt; ++i) {
auto@ other = getTrait(i);
for(uint n = 0, ncnt = other.hooks.length; n < ncnt; ++n) {
const ITraitEffect@ oth = other.hooks[n];
if(oth is cmp) {
@ourType = other;
break;
}
}
if(only_available.boolean && !other.available)
continue;
if(other is ourType)
continue;
if(other.cost != 0)
continue;
if(other.id == uint(ignore_trait.integer))
continue;
if(other.unique == unique_type.str) {
total += 1;
if(randomd() < 1.0 / total)
@type = other;
}
}
if(type !is null)
emp.addTrait(type.id, doPreInit=true);
}
#section all
};
tidy final class RepeatLargestFlagshipBuiltSize : TraitEffect {
Document doc("Repeat a trigger every time a flagship larger than the previously biggest flagship is built, repeats depending on size.");
Argument hookID("Hook", AT_Hook, "bonus_effects::EmpireTrigger");
EmpireTrigger@ hook;
Argument repeat_per(AT_Decimal, "10", doc="Repeat once for every time the largest ever design goes up by this amount.");
Argument start_from(AT_Decimal, "100", doc="Don't count size increments below this total size (starting ships).");
#section server
bool instantiate() override {
@hook = cast<EmpireTrigger>(parseHook(hookID.str, "bonus_effects::"));
return TraitEffect::instantiate();
}
void init(Empire& emp, any@ data) const override {
double maxSize = 0;
data.store(maxSize);
}
void tick(Empire& emp, any@ data, double time) const override {
double previous = 0;
data.retrieve(previous);
uint ind = randomi(0, emp.fleetCount-1);
Ship@ fleet = cast<Ship>(emp.fleets[ind]);
if(fleet !is null && !fleet.isStation) {
const Design@ dsg = fleet.blueprint.design;
if(dsg !is null && dsg.owner is emp) {
double flSize = floor(dsg.size / repeat_per.decimal) * repeat_per.decimal;
if(flSize > previous) {
int repeats = floor(flSize / repeat_per.decimal);
repeats -= floor(max(previous, start_from.decimal) / repeat_per.decimal);
if(repeats > 0) {
for(uint i = 0; i < uint(repeats); ++i)
hook.activate(null, emp);
}
data.store(flSize);
}
}
}
}
void save(any@ data, SaveFile& file) const override {
double val = 0;
data.retrieve(val);
file << val;
}
void load(any@ data, SaveFile& file) const override {
double val = 0;
file >> val;
data.store(val);
}
#section all
};
class ClearFirstBudget : TraitEffect {
Document doc("Make sure the first budget of the game for this empire is 0.");
#section server
void tick(Empire& emp, any@ data, double time) const override {
if(gameTime < 2.0)
emp.consumeBudget(emp.RemainingBudget, false);
}
#section all
};
class PeaceWithRemnants : TraitEffect {
Document doc("This empire will not be attacked by remnants.");
#section server
void init(Empire& emp, any@ data) const override {
Creeps.setHostile(emp, false);
emp.setHostile(Creeps, false);
}
#section all
};
class VisData {
set_int visionSystems;
array<int> list;
uint index = 0;
}
class GrantVisionTradeRange : TraitEffect {
Document doc("You automatically get vision over all systems within your trade range, even if you have no objects there.");
#section server
void init(Empire& emp, any@ data) const override {
VisData info;
data.store(@info);
}
void tick(Empire& emp, any@ data, double time) const override {
VisData@ info;
data.retrieve(@info);
for(uint n = 0; n < 50; ++n) {
info.index = (info.index+1) % systemCount;
auto@ sys = getSystem(info.index);
bool has = info.visionSystems.contains(info.index);
bool should = hasTradeAdjacent(emp, sys.object);
if(has != should) {
if(should) {
info.visionSystems.insert(info.index);
info.list.insertLast(info.index);
sys.object.grantVision(emp);
}
else {
info.visionSystems.erase(info.index);
info.list.remove(info.index);
sys.object.revokeVision(emp);
}
}
}
}
void save(any@ data, SaveFile& file) const override {
VisData@ info;
data.retrieve(@info);
uint cnt = info.list.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << info.list[i];
}
void load(any@ data, SaveFile& file) const override {
VisData info;
data.store(@info);
uint cnt = 0;
file >> cnt;
info.list.length = cnt;
for(uint i = 0; i < cnt; ++i) {
file >> info.list[i];
info.visionSystems.insert(info.list[i]);
}
}
#section all
};