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

4124 lines
97 KiB
ActionScript

#priority init 2000
import hooks;
import saving;
import icons;
import util.formatting;
from settings.map_lib import SystemDesc;
import const SystemDesc@ getClosestSystem(const vec3d& point, Empire& presence, bool trade = false) from "system_pathing";
// {{{ Influence cards
export InfluenceCardClass;
export InfluenceCardSide;
export InfluenceVariableMode;
export InfluenceCardType;
export InfluenceCard;
export InfluenceCardEffect;
export InfluenceSideEquals;
export StackInfluenceCard;
export InfluenceCardPlayEvent;
export Targets;
export INDETERMINATE;
export getInfluenceCardClassSprite;
export getInfluenceCardClassTooltip;
export getInfluenceCardRarityColor;
export getInfluenceCardTypeCount;
export getInfluenceCardType;
export getInfluenceDeckSize;
export getDistributedInfluenceCardType;
#section server-side
import Empire@ getSenateLeader() from "influence_global";
#section all
//{{{ Types
enum InfluenceCardClass {
ICC_Action,
ICC_Vote,
ICC_Effect,
ICC_Support,
ICC_Event,
ICC_Instant,
ICC_Misc,
ICC_COUNT
};
enum InfluenceCardRarity {
ICR_Basic,
ICR_Common,
ICR_Uncommon,
ICR_Rare,
ICR_Epic,
ICR_COUNT
};
enum InfluenceCardSide {
ICS_Support,
ICS_Oppose,
ICS_Both,
ICS_Neutral,
ICS_COUNT
};
enum InfluenceVariableMode {
IVM_None,
IVM_Property,
IVM_PurchaseCost,
};
bool InfluenceSideEquals(InfluenceCardSide spec, bool side) {
switch(spec) {
case ICS_Oppose: return side == false;
case ICS_Support: return side == true;
case ICS_Both: return false;
case ICS_Neutral: return false;
}
return false;
}
const int INDETERMINATE = INT_MIN;
tidy final class InfluenceCardType {
uint id;
string ident;
string name;
string description;
string dlc;
InfluenceCardClass cls = ICC_Misc;
bool leaderOnly = false;
Sprite icon;
Color color;
double frequency = -1;
InfluenceCardRarity rarity = ICR_Common;
bool collapseUses = true;
int basePurchaseCost = 0;
int qualityPurchaseCost = 0;
int placementPurchaseCost = 1;
int usesPurchaseCost = 0;
int sideTarget = -1;
InfluenceCardSide sideMode = ICS_Neutral;
int basePlayCost = 0;
int qualityPlayCost = 0;
int baseWeight = 0;
int qualityWeight = 0;
int minQuality = 1;
int maxQuality = 1;
bool canOverquality = true;
int minUses = 1;
int maxUses = 1;
uint points = 0;
Targets targets;
array<InfluenceCardEffect@> hooks;
array<Hook@> ai;
void init() {
for(uint i = 0, cnt = hooks.length; i < cnt; ++i) {
if(!hooks[i].instantiate())
error("Could not instantiate hook: "+addrstr(hooks[i])+" in "+ident);
hooks[i].initTargets(targets);
hooks[i].init(this);
}
for(uint i = 0, cnt = ai.length; i < cnt; ++i) {
if(!ai[i].instantiate())
error("Could not instantiate AI hook: "+addrstr(ai[i])+" in card "+ident);
ai[i].initTargets(targets);
}
}
//Create an empty card, with no random generation.
InfluenceCard@ create(int uses = 1, int quality = 0) const {
InfluenceCard card;
card.quality = quality == 0 ? minQuality : quality;
if(!canOverquality)
card.quality = clamp(card.quality, minQuality, maxQuality);
card.uses = uses;
card.set(this);
return card;
}
//Draw a random new influence card of this type,
//as generated by this card's generators.
InfluenceCard@ generate() const {
InfluenceCard card;
generate(card);
return card;
}
void generate(InfluenceCard@ card) const {
card.quality = minQuality + floor(sqr(randomd()) * double(maxQuality - minQuality + 1));
card.uses = minUses + floor(sqr(sqr(randomd())) * double(maxUses - minUses + 1));
card.set(this);
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
hooks[i].generate(card);
}
bool canGenerateOnStack() const {
for(uint i = 0, cnt = hooks.length; i < cnt; ++i) {
if(!hooks[i].canGenerateOnStack())
return false;
}
return true;
}
};
//}}}
//{{{ Cards
tidy class InfluenceCard : Serializable, Savable {
const InfluenceCardType@ type;
Targets targets;
int id = -1;
int uses = -1;
int quality = 0;
Empire@ owner;
array<any> data;
InfluenceCard() {
}
InfluenceCard(const InfluenceCardType@ Type) {
set(Type);
}
int opCmp(const InfluenceCard@ other) {
if(type.cls != other.type.cls)
return int(type.cls) - int(other.type.cls);
if(type.rarity != other.type.rarity)
return int(type.rarity) - int(other.type.rarity);
if(type !is other.type)
return int(type.id) - int(other.type.id);
if(other.quality != quality)
return other.quality - quality;
return id - other.id;
}
void set(const InfluenceCardType@ Type) {
@type = Type;
targets.set(type.targets);
data.length = type.hooks.length;
}
int get_extraQuality() const {
return quality - type.minQuality;
}
uint get_points() const {
uint pts = type.points;
pts += 10 * type.basePurchaseCost;
pts += 10 * type.qualityPurchaseCost * (quality - type.minQuality);
return pts;
}
void resetData() {
data.length = 0;
data.length = type.hooks.length;
}
bool isActiveEffect(const InfluenceVote@ vote = null) const {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(type.hooks[i].isActiveEffect(this, null, vote))
return true;
}
return false;
}
//Networking
InfluenceCard(Message& msg) {
read(msg);
}
void write(Message& msg) {
msg.writeLimited(type.id,getInfluenceCardTypeCount()-1);
targets.writeData(msg, type.targets);
msg << id;
msg << owner;
msg.writeSignedSmall(uses);
msg.writeSignedSmall(quality);
}
void read(Message& msg) {
@type = getInfluenceCardType(msg.readLimited(getInfluenceCardTypeCount()-1));
targets.readData(msg, type.targets);
data.length = type.hooks.length;
msg >> id;
msg >> owner;
uses = msg.readSignedSmall();
quality = msg.readSignedSmall();
}
//Saving
InfluenceCard(SaveFile& file) {
load(file);
}
void save(SaveFile& file) {
file.writeIdentifier(SI_InfluenceCard, type.id);
targets.saveData(file, type.targets);
file << id;
file << uses;
file << quality;
file << owner;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].save(this, file);
}
void load(SaveFile& file) {
uint typeId = file.readIdentifier(SI_InfluenceCard);
@type = getInfluenceCardType(typeId);
data.length = type.hooks.length;
targets.loadData(file, type.targets);
file >> id;
file >> uses;
file >> quality;
file >> owner;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].load(this, file);
}
//Formatting
string formatTitle(bool pretty = true) const {
return targets.format(type.name, pretty);
}
string formatDescription(bool pretty = true) const {
return targets.format(type.description, pretty);
}
string formatBlurb(int uses = 0) const {
string title = formatTitle();
if(uses == 0)
uses = this.uses;
if(uses < 0)
title += " ("+locale::USES_UNLIMITED+")";
else if(uses > 1)
title += " ("+uses+"x)";
int qual = extraQuality;
if(qual > 0) {
title += " ";
for(int i = 0; i < qual; ++i)
title += "*";
}
title = format("[color=$2]$1[/color]", title, toString(type.color));
return title;
}
string formatTooltip(Empire@ purchaseEmpire = null, bool showUses = true) const {
string tt;
tt = formatTitle();
if(extraQuality != 0) {
tt += " ";
for(int i = 0, cnt = extraQuality; i < cnt; ++i)
tt += "*";
}
if(uses > 1 && showUses)
tt += format(" ($1x)", toString(uses));
tt = format("[b][font=Medium][color=$2]$1[/color][/font][/b]\n", tt, toString(type.color));
tt += formatDescription();
tt += "\n";
Sprite sprt;
string name, tooltip, text;
bool highlight = false;
if(purchaseEmpire !is null) {
int purchaseCost = getPurchaseCost(purchaseEmpire);
if(purchaseCost != 0)
tt += "\n"+formatVariable(icons::Influence,
locale::CARD_PURCHASE_INFLUENCE, "",
format(locale::CARD_COST, toString(purchaseCost)), false);
}
if(getCostVariable(sprt, name, tooltip, text, highlight))
tt += "\n"+formatVariable(sprt, name, tooltip, text, highlight);
if(getWeightVariable(sprt, name, tooltip, text, highlight))
tt += "\n"+formatVariable(sprt, name, tooltip, text, highlight);
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
auto mode = type.hooks[i].getVariable(this, null, sprt, name, tooltip, text, highlight);
if(mode != IVM_None)
tt += "\n"+formatVariable(sprt, name, tooltip, text, highlight);
}
return tt;
}
string formatVariable(const Sprite& sprt, const string& name, const string& tooltip, const string& text, bool highlight) const {
string value = text;
if(highlight)
value = format("[color=#ffcd00][b]$1[/b][/color]", value);
return format("[img=$3;20/][b] $1[/b][offset=180]$2[/offset]", name, value, getSpriteDesc(sprt));
}
bool getCostVariable(Sprite& sprt, string& name, string& tooltip, string& text, bool& highlight, const InfluenceVote@ vote = null) const {
int cost = getPlayCost(vote, null);
if(cost == 0)
return false;
sprt = icons::InfluencePlayCost;
name = locale::CARD_DESC_COST;
if(cost == INDETERMINATE) {
text = "~";
tooltip = locale::INFLUENCE_TT_PLAY_COST_UNKNOWN;
}
else {
if(vote !is null && getSide(null) == ICS_Both) {
int p = cost + vote.positiveCostPenalty, n = cost + vote.negativeCostPenalty;
bool canP = vote.canVote(owner, true), canN = vote.canVote(owner, false);
if(canP && canN && p != n)
text = toString(min(p,n))+"-"+toString(max(p,n));
else if(canP)
text = toString(p);
else if(canN)
text = toString(n);
else
text = toString(cost);
}
else {
text = toString(cost);
}
tooltip = format(locale::INFLUENCE_TT_PLAY_COST, text);
}
highlight = false;
return true;
}
bool getWeightVariable(Sprite& sprt, string& name, string& tooltip, string& text, bool& highlight, const InfluenceVote@ vote = null) const {
int weight = getWeight(vote, null);
if(weight == 0)
return false;
sprt = icons::InfluenceWeight;
name = locale::CARD_DESC_WEIGHT;
if(weight == INDETERMINATE) {
text = "~";
tooltip = locale::INFLUENCE_TT_WEIGHT_UNKNOWN;
}
else {
text = toString(weight);
tooltip = format(locale::INFLUENCE_TT_WEIGHT, text);
}
highlight = quality > type.minQuality && type.qualityWeight > 0;
return true;
}
bool checkTargets(const Targets@ check) const {
if(check is null)
return true;
if(check.targets.length != type.targets.targets.length)
return false;
for(uint i = 0, cnt = check.targets.length; i < cnt; ++i) {
if(!isValidTarget(i, check.targets[i]))
return false;
}
return true;
}
//Hooks
bool canPlay(const Targets@ targets) const {
if(type.cls == ICC_Support)
return false;
if(owner is null)
return false;
if(type.leaderOnly && getSenateLeader() !is owner)
return false;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(!type.hooks[i].canPlay(this, targets))
return false;
}
if(!checkTargets(targets))
return false;
return true;
}
bool isValidTarget(uint index, const Target@ targ) const {
if(index >= targets.targets.length)
return false;
if(targ.type != type.targets.targets[index].type)
return false;
if(!targ.filled)
return false;
if(targets.targets[index].filled && targets.targets[index] != targ)
return false;
if(targ.type == TT_Empire && targ.emp !is null && owner !is null
&& owner.ContactMask & targ.emp.mask == 0) {
return false;
}
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(!type.hooks[i].isValidTarget(this, index, targ))
return false;
}
return true;
}
void targetDefaults(Targets@ targets) const {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].targetDefaults(this, targets);
}
bool getTargetError(uint index, const Target@ targ, string& str) const {
if(index >= type.targets.length)
return false;
if(targ.type != type.targets[index].type)
return false;
if(!targ.filled)
return false;
if(type.targets[index].filled && type.targets[index] != targ)
return false;
if(targ.type == TT_Empire && targ.emp !is null && owner !is null
&& owner.ContactMask & targ.emp.mask == 0)
return false;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(!type.hooks[i].isValidTarget(this, index, targ)) {
str = type.hooks[i].getFailReason(this, index, targ);
return true;
}
}
return false;
}
string getTargetError(const Targets@ check) const {
string str;
for(uint i = 0, cnt = check.targets.length; i < cnt; ++i) {
if(getTargetError(i, check.targets[i], str))
return str;
}
return str;
}
#section server
void play(Targets@ targets) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onPlay(this, targets);
if(uses > 0) {
uses -= 1;
lose(1, true);
}
}
void gain(int uses, bool wasBuy) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onGain(this, uses, wasBuy);
if(uses > 0) {
owner.DiplomacyPoints += points * uses;
owner.points += points * uses;
}
if(wasBuy) {
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
auto@ emp = getEmpire(i);
if(!emp.major)
continue;
if(emp is owner)
continue;
double rebate = emp.RebateInfluenceBuys;
if(rebate != 0)
emp.addInfluence(+rebate);
double copyChance = emp.InfluenceBuysCopyChance;
if(copyChance != 0 && randomd() >= copyChance)
emp.gainCard(type.id, 1, 0);
}
}
}
void lose(int uses, bool wasPlayed) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onLose(this, uses, wasPlayed);
if(uses > 0) {
owner.DiplomacyPoints -= points * uses;
owner.points -= points * uses;
}
}
void enterStack() {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onEnterStack(this);
}
void tickStack(double time) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onStackTick(this, time);
}
void leaveStack(bool wasBuy) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onLeaveStack(this, wasBuy);
}
bool tick(double time) {
bool changed = false;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(type.hooks[i].onTick(this, time))
changed = true;
}
return changed;
}
void play(InfluenceVote@ vote, Targets@ targets) {
auto side = getSide(targets);
if(side == ICS_Both) {
throw("Ambiguous side for vote card.");
return;
}
//Do weight voting
int weight = 0;
if(side != ICS_Neutral) {
weight = getWeight(vote, targets);
if(side != ICS_Support)
weight *= -1;
vote.vote(owner, weight);
}
//Trigger hooks
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onPlay(this, vote, targets, weight);
//Check if we should be added as an effect
bool addEffect = false;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(type.hooks[i].isVoteEffect) {
addEffect = true;
break;
}
}
if(addEffect) {
auto@ eff = vote.addCardEffect(this, targets);
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onVoteEffect(eff, vote);
}
//Remove use
if(uses > 0) {
uses -= 1;
lose(1, true);
}
}
void voteTick(InfluenceVote@ vote, double time) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onVoteTick(this, vote, time);
}
void voteEvent(InfluenceVote@ vote, InfluenceVoteEvent@ evt) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onVoteEvent(this, vote, evt);
}
void voteEnd(InfluenceVote@ vote, bool passed, bool withdrawn) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onVoteEnd(this, vote, passed, withdrawn);
}
#section all
InfluenceCardSide getSide(const Targets@ targets) const {
if(type.sideMode == ICS_Neutral)
return ICS_Neutral;
if(type.sideTarget >= 0 && targets !is null) {
auto@ targ = targets.targets[type.sideTarget];
if(targ.filled)
return targ.side ? ICS_Support : ICS_Oppose;
}
return type.sideMode;
}
bool canPurchase(Empire@ empire, int placement = 0) const {
if(type.cls == ICC_Event)
return false;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(!type.hooks[i].canPurchase(this, empire, placement))
return false;
}
for(uint i = 0, cnt = targets.length; i < cnt; ++i) {
auto@ targ = targets[i];
if(targ.type == TT_Empire && targ.emp !is null && empire !is null
&& empire.ContactMask & targ.emp.mask == 0)
return false;
}
return true;
}
bool hasPurchaseCost(Empire@ empire, int placement = 0) const {
if(getPurchaseCost(empire, placement) > empire.Influence)
return false;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(!type.hooks[i].hasPurchaseCost(this, empire, placement))
return false;
}
return true;
}
bool canPlay(const InfluenceVote@ vote, const Targets@ targets) const {
//Can generically always be played
if(vote is null)
return true;
if(owner is null)
return false;
if(type.leaderOnly && getSenateLeader() !is owner)
return false;
if(!vote.isPresent(owner))
return false;
//Only support cards go in votes
if(type.cls != ICC_Support)
return false;
//Check voting limitation
auto side = getSide(targets);
switch(side) {
case ICS_Support:
if(!vote.canVote(owner, true))
return false;
break;
case ICS_Oppose:
if(!vote.canVote(owner, false))
return false;
break;
case ICS_Both:
if(!vote.canVote(owner, false) && !vote.canVote(owner, true))
return false;
break;
}
//Check hooks
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(!type.hooks[i].canPlay(this, vote, targets))
return false;
}
//Check targets
if(!checkTargets(targets))
return false;
//Check vote
if(!vote.canPlayOn(this, targets, side))
return false;
return true;
}
bool canPlayOn(const InfluenceVote@ vote, const InfluenceCard@ other, const Targets@ targets, InfluenceCardSide side) const {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
if(!type.hooks[i].canPlayOn(this, vote, other, targets, side))
return false;
return true;
}
bool canVoteIn(const InfluenceVote@ vote, Empire& emp, bool support) const {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
if(!type.hooks[i].canVoteIn(this, vote, emp, support))
return false;
return true;
}
bool purchaseConsume(Empire@ byEmpire, int placement = 0) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(!type.hooks[i].purchaseConsume(this, byEmpire, placement)) {
for(uint n = 0; n < i; ++n)
type.hooks[i].purchaseConsumeRewind(this, byEmpire, placement);
return false;
}
}
return true;
}
bool playConsume(Targets@ targets, InfluenceVote@ vote = null) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(!type.hooks[i].playConsume(this, targets, vote)) {
for(uint n = 0; n < i; ++n)
type.hooks[i].playConsumeRewind(this, targets, vote);
return false;
}
}
return true;
}
int getPurchaseCost(Empire@ byEmpire, int placement = 0, int uses = 0) const {
int cost = type.basePurchaseCost;
cost += type.placementPurchaseCost * placement;
cost += type.qualityPurchaseCost * extraQuality;
if(byEmpire !is null)
cost += int(byEmpire.ExtraInfluenceBuyCost);
if(uses == 0) {
if(this.uses > 1)
cost += type.usesPurchaseCost * (this.uses - 1);
}
else {
cost += type.usesPurchaseCost * (uses - 1);
}
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
cost += type.hooks[i].getPurchaseCost(this, byEmpire, placement);
return cost;
}
int getPlayCost(const InfluenceVote@ vote = null, const Targets@ targets = null) const {
int cost = type.basePlayCost;
if(cost == INDETERMINATE)
return INDETERMINATE;
int minimum = 0;
cost += type.qualityPlayCost * extraQuality;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
int mod = type.hooks[i].getPlayCost(this, vote, targets);
if(mod == INDETERMINATE)
return INDETERMINATE;
if(mod < 0)
minimum = 1;
cost += mod;
}
if(owner !is null) {
if(type.cls == ICC_Vote)
cost += owner.VoteCardCostMod;
else if(type.cls == ICC_Support)
cost += owner.SupportCardCostMod;
}
if(vote !is null && type.cls == ICC_Support) {
auto side = getSide(targets);
if(side == ICS_Support)
cost += vote.positiveCostPenalty;
else if(side == ICS_Oppose)
cost += vote.negativeCostPenalty;
}
return max(cost, minimum);
}
int getWeight(const InfluenceVote@ vote = null, const Targets@ targets = null) const {
int weight = type.baseWeight;
if(weight == INDETERMINATE)
return INDETERMINATE;
int minimum = 0;
weight += type.qualityWeight * extraQuality;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
int mod = type.hooks[i].getWeight(this, vote, targets);
if(mod == INDETERMINATE)
return INDETERMINATE;
if(mod < 0)
minimum = 1;
weight += mod;
}
return max(weight, minimum);
}
bool canCollapseUses(const InfluenceCard@ other) const {
if(other.type !is type)
return false;
if(other.owner !is owner)
return false;
if(targets != other.targets)
return false;
if(quality != other.quality)
return false;
if(uses <= 0 || other.uses <= 0)
return false;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
if(!type.hooks[i].canCollapseUses(this, other))
return false;
return true;
}
};
//}}}
//{{{ Effects
class InfluenceCardEffect : Hook {
uint hookIndex = 0;
void init(InfluenceCardType@ type) {
}
bool canGenerateOnStack() {
return true;
}
//When a card or use is gained by an empire.
// wasBuy indicates if the card was bought by the
// player or gained otherwise.
void onGain(InfluenceCard@ card, int uses, bool wasBuy) const {
}
//When a card or use is lost by an empire.
// wasPlayed indicates if the card was lost through being
// played by the player, or otherwise.
void onLose(InfluenceCard@ card, int uses, bool wasPlayed) const {
}
//Whether a target is valid
bool isValidTarget(const InfluenceCard@ card, uint index, const Target@ targets) const {
return true;
}
string getFailReason(const InfluenceCard@ card, uint index, const Target@ targ) const {
return "";
}
//Whether to display in the active effects list
bool isActiveEffect(const InfluenceCard@ card, const InfluenceVoteEvent@ event, const InfluenceVote@ vote) const {
return false;
}
//Setting the default values for targets. Do not mark them filled.
void targetDefaults(const InfluenceCard@ card, Targets@ targets) const {
}
//Whether a card can be played
bool canPlay(const InfluenceCard@ card, const Targets@ targets) const {
return true;
}
//When a card is triggered, after paying its cost.
void onPlay(InfluenceCard@ card, Targets@ targets) const {
}
//When a card enters the stack.
void onEnterStack(InfluenceCard@ card) const {
}
//When a card on the stack ticks.
void onStackTick(InfluenceCard@ card, double time) const {
}
//When a card leaves the stack.
void onLeaveStack(InfluenceCard@ card, bool wasBuy) const {
}
//Cards ticking inside the empire's pool. Return true to indicate that
//something about the card was changed by the tick.
bool onTick(InfluenceCard@ card, double time) const {
return false;
}
//Whether this card can be purchased
bool canPurchase(const InfluenceCard@ card, Empire@ empire, int placement) const {
return true;
}
//Whether the empire currently has enough to purchase the card
bool hasPurchaseCost(const InfluenceCard@ card, Empire@ empire, int placement) const {
return true;
}
//Cost to add to purchasing
int getPurchaseCost(const InfluenceCard@ card, Empire@ byEmpire, int placement) const {
return 0;
}
//Consumption on purchasing
bool purchaseConsume(InfluenceCard@ card, Empire@ byEmpire, int placement) const {
return true;
}
void purchaseConsumeRewind(InfluenceCard@ card, Empire@ byEmpire, int placement) const {
}
//Consumption on purchasing
bool playConsume(InfluenceCard@ card, Targets@ targets, InfluenceVote@ vote = null) const {
return true;
}
void playConsumeRewind(InfluenceCard@ card, Targets@ targets, InfluenceVote@ vote = null) const {
}
//Cost to add to playing
int getPlayCost(const InfluenceCard@ card, const InfluenceVote@ vote, const Targets@ targets) const {
return 0;
}
//Variable display on card
InfluenceVariableMode getVariable(const InfluenceCard@ card, const InfluenceVote@ vote, Sprite& sprt, string& name, string& tooltip, string& text, bool& highlight) const {
return IVM_None;
}
//Weight to add to voting
int getWeight(const InfluenceCard@ card, const InfluenceVote@ vote, const Targets@ targets) const {
return 0;
}
//Whether the card can be played into a vote
bool canPlay(const InfluenceCard@ card, const InfluenceVote@ vote, const Targets@ targets) const {
return canPlay(card, targets);
}
//When the card gets played into a vote
void onPlay(InfluenceCard@ card, InfluenceVote@ vote, Targets@ targets, int weight) const {
onPlay(card, targets);
}
//Whether this card should receive onVote..() events.
bool get_isVoteEffect() const {
return false;
}
//When the card is first placed into a vote as an effect
void onVoteEffect(InfluenceCard@ card, InfluenceVote@ vote) const {
}
//When the card played into a vote ticks
void onVoteTick(InfluenceCard@ card, InfluenceVote@ vote, double time) const {
}
//When an event occurs on the vote this card is played in
void onVoteEvent(InfluenceCard@ card, InfluenceVote@ vote, InfluenceVoteEvent@ event) const {
}
//When the vote this card is played in ends
void onVoteEnd(InfluenceCard@ card, InfluenceVote@ vote, bool passed, bool withdrawn) const {
}
//Whether a card can be played in the vote this card is in
bool canPlayOn(const InfluenceCard@ card, const InfluenceVote@ vote, const InfluenceCard@ other, const Targets@ targets, InfluenceCardSide side) const {
return true;
}
//Whether an empire can vote in the vote this card is in
bool canVoteIn(const InfluenceCard@ card, const InfluenceVote@ vote, Empire& emp, bool support) const {
return true;
}
//Formatting for vote events
bool formatEvent(const InfluenceCard@ card, const InfluenceVoteEvent@ evt, string& text) const {
return false;
}
//Formatting for notifications
bool formatNotification(const InfluenceCard@ card, const InfluenceCardPlayEvent@ event, const ICardNotification@ notification, string& text) const {
return false;
}
//Save hook data
void save(InfluenceCard@ card, SaveFile& file) const {
}
void save(InfluenceVote@ vote, SaveFile& file) const {
}
//Load hook data
void load(InfluenceCard@ card, SaveFile& file) const {
}
void load(InfluenceVote@ vote, SaveFile& file) const {
}
//Generate a randomized card
void generate(InfluenceCard@ card) const {
}
//Whether the uses can be collapsed here
bool canCollapseUses(const InfluenceCard@ into, const InfluenceCard@ other) const {
return true;
}
};
//}}}
//{{{Secondary structures
tidy final class StackInfluenceCard : InfluenceCard {
int placement = 0;
Empire@ purchasedBy;
int getPlacement(Empire@ emp) const {
return max(placement + int(emp.InfluencePlacementMod), 0);
}
int getPurchaseCost(Empire@ byEmpire, int placement = 0, int uses = 0) const override {
return InfluenceCard::getPurchaseCost(byEmpire, getPlacement(byEmpire), uses);
}
bool purchaseConsume(Empire@ byEmpire, int placement = 0) override {
return InfluenceCard::purchaseConsume(byEmpire, getPlacement(byEmpire));
}
bool canPurchase(Empire@ empire, int placement = 0) const override {
if(purchasedBy !is null)
return false;
return InfluenceCard::canPurchase(empire, getPlacement(empire));
}
bool hasPurchaseCost(Empire@ empire, int placement = 0) const override {
if(purchasedBy !is null)
return false;
return InfluenceCard::hasPurchaseCost(empire, getPlacement(empire));
}
StackInfluenceCard() {
super();
}
StackInfluenceCard(Message& msg) {
read(msg);
}
void write(Message& msg) {
InfluenceCard::write(msg);
msg << placement;
msg << purchasedBy;
}
void read(Message& msg) {
InfluenceCard::read(msg);
msg >> placement;
msg >> purchasedBy;
}
StackInfluenceCard(SaveFile& file) {
load(file);
}
void save(SaveFile& file) {
InfluenceCard::save(file);
file << placement;
file << purchasedBy;
}
void load(SaveFile& file) {
InfluenceCard::load(file);
file >> placement;
file >> purchasedBy;
}
};
interface InfluenceStore {
int addCard(Empire& emp, InfluenceCard@ card, bool wasBuy = true);
void playCard(Empire& emp, int id, Targets@ targets, bool pay = true, InfluenceVote@ vote = null);
};
tidy class InfluenceCardPlayEvent : Serializable, Savable {
InfluenceCard card;
Targets targets;
InfluenceCardPlayEvent() {
}
InfluenceCardPlayEvent(InfluenceCard@ Card, Targets@ Targets) {
card = Card;
card.resetData();
targets = Targets;
card.targets = Targets;
}
//Networking
InfluenceCardPlayEvent(Message& msg) {
read(msg);
}
void write(Message& msg) {
msg << card;
msg << targets;
}
void read(Message& msg) {
msg >> card;
msg >> targets;
}
//Saving
InfluenceCardPlayEvent(SaveFile& file) {
load(file);
}
void save(SaveFile& file) {
file << card;
file << targets;
}
void load(SaveFile& file) {
file >> card;
file >> targets;
}
string formatNotification(const ICardNotification@ notification) const {
string text;
for(uint i = 0, cnt = card.type.hooks.length; i < cnt; ++i) {
if(card.type.hooks[i].formatNotification(card, this, notification, text))
return text;
}
return text;
}
bool wasEventOf(const InfluenceCard@ other, bool matchTargets = false) const {
if(other.id != card.id)
return false;
if(other.owner !is card.owner)
return false;
if(matchTargets && card.targets != other.targets)
return false;
return true;
}
};
interface ICardNotification {
};
//}}}
//{{{ Initialization
Sprite getInfluenceCardClassSprite(InfluenceCardClass cls) {
switch(cls) {
case ICC_Action: return Sprite(spritesheet::CardCategoryIcons, 0);
case ICC_Effect: return Sprite(spritesheet::CardCategoryIcons, 1);
case ICC_Instant: return Sprite(spritesheet::CardCategoryIcons, 2);
case ICC_Support: return Sprite(spritesheet::CardCategoryIcons, 3);
case ICC_Vote: return Sprite(spritesheet::CardCategoryIcons, 4);
case ICC_Event: return Sprite(spritesheet::CardCategoryIcons, 5);
}
return Sprite();
}
string getInfluenceCardClassTooltip(InfluenceCardClass cls) {
switch(cls) {
case ICC_Action: return locale::INFLUENCE_TT_ACTION;
case ICC_Instant: return locale::INFLUENCE_TT_INSTANT;
case ICC_Vote: return locale::INFLUENCE_TT_VOTE;
case ICC_Effect: return locale::INFLUENCE_TT_EFFECT;
case ICC_Event: return locale::INFLUENCE_TT_EVENT;
case ICC_Support: return locale::INFLUENCE_TT_SUPPORT;
}
return "";
}
Color getInfluenceCardRarityColor(uint rarity) {
switch(rarity) {
case ICR_Basic: case ICR_Common:
return Color(0xffffffff);
case ICR_Uncommon: return Color(0x80baffff);
case ICR_Rare: return Color(0xe580ffff);
case ICR_Epic: return Color(0xffe680ff);
}
return Color(0xffffffff);
}
const array<int> RARITY_DEFAULTS = {10, 4, 3, 2, 1};
int totalCardFrequency = 0;
array<InfluenceCardType@> cardList;
dictionary cardIdents;
uint getInfluenceDeckSize() {
return totalCardFrequency;
}
uint getInfluenceCardTypeCount() {
return cardList.length;
}
const InfluenceCardType@ getInfluenceCardType(uint id) {
if(id >= cardList.length)
return null;
return cardList[id];
}
const InfluenceCardType@ getInfluenceCardType(const string& ident) {
InfluenceCardType@ type;
cardIdents.get(ident, @type);
return type;
}
int getInfluenceCardID(const string& ident) {
InfluenceCardType@ type;
cardIdents.get(ident, @type);
if(type is null)
return -1;
return type.id;
}
string getInfluenceCardIdent(int id) {
if(uint(id) >= cardList.length)
return "";
return cardList[id].ident;
}
void addInfluenceCardType(InfluenceCardType@ type) {
type.id = cardList.length;
cardIdents.set(type.ident, @type);
cardList.insertLast(type);
if(type.frequency < 0)
type.frequency = RARITY_DEFAULTS[type.rarity];
totalCardFrequency += int(floor(type.frequency));
if(type.sideMode == ICS_Both)
type.targets.add("VoteSide", TT_Side);
for(uint i = 0, cnt = type.targets.targets.length; i < cnt; ++i) {
if(type.targets.targets[i].name.equals_nocase("voteside")) {
type.sideTarget = int(i);
break;
}
}
}
const InfluenceCardType@ getDistributedInfluenceCardType() {
double num = randomd(0, totalCardFrequency);
for(uint i = 0, cnt = cardList.length; i < cnt; ++i) {
auto@ type = cardList[i];
double freq = floor(type.frequency);
if(num <= freq)
return type;
num -= freq;
}
return cardList[cardList.length-1];
}
void readCardLine(const string& line, InfluenceCardType@ type, ReadFile@ file) {
auto@ hook = cast<InfluenceCardEffect>(parseHook(line, "card_effects::", instantiate=false, file=file));
if(hook !is null) {
hook.hookIndex = type.hooks.length;
type.hooks.insertLast(hook);
}
}
bool readCard(ReadFile@ file) {
InfluenceCardType@ type;
do {
if(file.fullLine) {
if(type !is null)
readCardLine(file.line, type, file);
else
file.error("Unexpected line: "+escape(file.line));
}
else if(file.key == "Card") {
if(type !is null)
addInfluenceCardType(type);
@type = InfluenceCardType();
type.ident = file.value;
}
else if(file.key == "Name") {
type.name = localize(file.value);
}
else if(file.key == "Description") {
type.description = localize(file.value);
}
else if(file.key == "Color") {
type.color = toColor(file.value);
}
else if(file.key == "Icon") {
type.icon = getSprite(file.value);
}
else if(file.key == "DLC") {
type.dlc = file.value;
}
else if(file.key == "Frequency") {
type.frequency = toDouble(file.value);
}
else if(file.key == "AI") {
auto@ hook = parseHook(file.value, "ai.diplomacy::", instantiate=false, file=file);
if(hook !is null)
type.ai.insertLast(hook);
}
else if(file.key == "Collapse Uses") {
type.collapseUses = toBool(file.value);
}
else if(file.key == "Points") {
type.points = toUInt(file.value);
}
else if(file.key == "Leader Only") {
type.leaderOnly = toBool(file.value);
}
else if(file.key == "Class") {
if(file.value.equals_nocase("support")) {
type.cls = ICC_Support;
}
else if(file.value.equals_nocase("vote")) {
type.cls = ICC_Vote;
}
else if(file.value.equals_nocase("effect")) {
type.cls = ICC_Effect;
}
else if(file.value.equals_nocase("action")) {
type.cls = ICC_Action;
}
else if(file.value.equals_nocase("event")) {
type.cls = ICC_Event;
}
else if(file.value.equals_nocase("instant")) {
type.cls = ICC_Instant;
}
else if(file.value.equals_nocase("misc")) {
type.cls = ICC_Misc;
}
else {
file.error("Invalid card class: "+file.value);
}
}
else if(file.key == "Rarity") {
if(file.value.equals_nocase("basic")) {
type.rarity = ICR_Basic;
}
else if(file.value.equals_nocase("common")) {
type.rarity = ICR_Common;
}
else if(file.value.equals_nocase("uncommon")) {
type.rarity = ICR_Uncommon;
}
else if(file.value.equals_nocase("rare")) {
type.rarity = ICR_Rare;
}
else if(file.value.equals_nocase("epic")) {
type.rarity = ICR_Epic;
}
else {
file.error("Invalid card rarity: "+file.value);
}
}
else if(file.key == "Side") {
if(file.value.equals_nocase("both")) {
type.sideMode = ICS_Both;
}
else if(file.value.equals_nocase("support")) {
type.sideMode = ICS_Support;
}
else if(file.value.equals_nocase("oppose")) {
type.sideMode = ICS_Oppose;
}
else if(file.value.equals_nocase("neutral")) {
type.sideMode = ICS_Neutral;
}
else {
file.error("Invalid card side: "+file.value);
}
}
else if(file.key == "Base Purchase Cost") {
type.basePurchaseCost = toInt(file.value);
}
else if(file.key == "Quality Purchase Cost") {
type.qualityPurchaseCost = toInt(file.value);
}
else if(file.key == "Placement Purchase Cost") {
type.placementPurchaseCost = toInt(file.value);
}
else if(file.key == "Uses Purchase Cost") {
type.usesPurchaseCost = toInt(file.value);
}
else if(file.key == "Base Play Cost") {
type.basePlayCost = toInt(file.value);
}
else if(file.key == "Quality Play Cost") {
type.qualityPlayCost = toInt(file.value);
}
else if(file.key == "Base Weight") {
type.baseWeight = toInt(file.value);
}
else if(file.key == "Quality Weight") {
type.qualityWeight = toInt(file.value);
}
else if(file.key == "Min Quality") {
type.minQuality = toInt(file.value);
}
else if(file.key == "Max Quality") {
type.maxQuality = toInt(file.value);
}
else if(file.key == "Can Overquality") {
type.canOverquality = toBool(file.value);
}
else if(file.key == "Min Uses") {
type.minUses = toInt(file.value);
}
else if(file.key == "Max Uses") {
type.maxUses = toInt(file.value);
}
else if(file.key == "Target") {
parseTarget(type.targets, file.value);
}
else {
if(file.line.findFirst("(") != -1) {
if(type !is null)
readCardLine(file.line, type, file);
else
file.error("Unexpected line: "+escape(file.line));
}
else {
if(type !is null)
addInfluenceCardType(type);
return false;
}
}
} while(file++);
if(type !is null)
addInfluenceCardType(type);
return true;
}
//}}}
// }}}
// {{{ Influence votes
export InfluenceVoteType;
export getInfluenceVoteTypeCount;
export getInfluenceVoteType;
export InfluenceVote;
export InfluenceVoteStub;
export InfluenceVoteEventType;
export InfluenceVoteEvent;
export InfluenceVoteEffect;
//{{{ Types
tidy final class InfluenceVoteType {
uint id;
string ident;
string name;
string description;
Sprite icon;
Color color;
Targets targets;
array<InfluenceVoteEffect@> hooks;
array<Hook@> ai;
void init() {
for(uint i = 0, cnt = hooks.length; i < cnt; ++i) {
if(!hooks[i].instantiate())
error("Could not instantiate hook: "+addrstr(hooks[i])+" in "+ident);
hooks[i].initTargets(targets);
hooks[i].init(this);
}
for(uint i = 0, cnt = ai.length; i < cnt; ++i) {
if(!ai[i].instantiate())
error("Could not instantiate AI hook: "+addrstr(ai[i])+" in vote "+ident);
ai[i].initTargets(targets);
}
}
};
//}}}
//{{{ Votes
tidy final class InfluenceVote : Serializable, Savable {
const InfluenceVoteType@ type;
Targets targets;
uint id = uint(-1);
array<any> data;
Empire@ startedBy;
double startedAt = -1.0;
double endedAt = -1.0;
double currentTime = 0;
bool active = false;
bool succeeded = false;
bool delta = false;
double positiveSpeed = 1.0;
double negativeSpeed = 1.0;
int positiveCostPenalty = 0;
int negativeCostPenalty = 0;
uint empiresPresent = ~0;
array<int> empireVotes(getEmpireCount(), 0);
array<double> contribPoints(getEmpireCount(), 0);
array<InfluenceVoteOffer@> offers;
uint nextOfferId = 0;
int totalFor = 0;
int totalAgainst = 0;
array<InfluenceVoteEvent@> events;
array<InfluenceCard@> effects;
InfluenceVote() {
}
InfluenceVote(const InfluenceVoteType@ Type) {
set(Type);
}
void set(const InfluenceVoteType@ Type) {
@type = Type;
targets.set(type.targets);
data.length = type.hooks.length;
}
bool checkTargets(const Targets@ check) const {
if(check is null)
return true;
if(check.targets.length != type.targets.targets.length)
return false;
for(uint i = 0, cnt = check.targets.length; i < cnt; ++i) {
if(!check.targets[i].filled)
return false;
if(check.targets[i].type != type.targets.targets[i].type)
return false;
}
return true;
}
//Handling
bool isPresent(Empire& emp) const {
return empiresPresent & emp.mask != 0;
}
int get_totalVote() const {
return totalFor - totalAgainst;
}
double get_minimumRemaining() const {
if(currentTime < 0.0)
return -config::INFLUENCE_FAIL_THRES - currentTime;
else
return config::INFLUENCE_PASS_THRES - currentTime;
}
double get_remainingTime() const {
if(totalFor > totalAgainst) {
double speed = positiveSpeed;
if(speed < 0.0001)
speed *= 100000.0;
return (config::INFLUENCE_PASS_THRES-currentTime) / speed;
}
else {
double speed = negativeSpeed;
if(speed < 0.0001)
speed *= 100000.0;
return (currentTime-config::INFLUENCE_FAIL_THRES) / speed;
}
}
Empire@ get_highestContributor() const {
Empire@ emp;
int highest = INT_MIN;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
int amt = empireVotes[i];
if(amt > highest) {
@emp = getEmpire(i);
highest = amt;
}
}
return emp;
}
Empire@ get_lowestContributor() const {
Empire@ emp;
int lowest = INT_MAX;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
int amt = empireVotes[i];
if(amt < lowest) {
@emp = getEmpire(i);
lowest = amt;
}
}
return emp;
}
Empire@ get_highestContribPoints() const {
Empire@ emp;
double highest = -INFINITY;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
double amt = contribPoints[i];
if(amt > highest) {
@emp = getEmpire(i);
highest = amt;
}
}
return emp;
}
Empire@ get_lowestContribPoints() const {
Empire@ emp;
double lowest = INFINITY;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
double amt = contribPoints[i];
if(amt < lowest) {
@emp = getEmpire(i);
lowest = amt;
}
}
return emp;
}
double get_highestContribPointsValue() const {
double highest = -INFINITY;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
double amt = contribPoints[i];
if(amt > highest)
highest = amt;
}
return highest;
}
double get_lowestContribPointsValue() const {
double lowest = INFINITY;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
double amt = contribPoints[i];
if(amt < lowest)
lowest = amt;
}
return lowest;
}
double getContribPoints(Empire& emp) const {
if(!emp.valid)
return 0;
return contribPoints[emp.index];
}
bool canVote(Empire& emp, bool support) const {
if(emp is startedBy && !support)
return false;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(!type.hooks[i].canVote(this, emp, support))
return false;
}
for(uint i = 0, cnt = effects.length; i < cnt; ++i) {
if(!effects[i].canVoteIn(this, emp, support))
return false;
}
return true;
}
bool canPlayOn(const InfluenceCard@ card, const Targets@ targets, InfluenceCardSide side) const {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(!type.hooks[i].canPlayOn(this, card, targets, side))
return false;
}
for(uint i = 0, cnt = effects.length; i < cnt; ++i) {
if(!effects[i].canPlayOn(this, card, targets, side))
return false;
}
return true;
}
int getVoteFrom(Empire& emp) const {
if(!emp.valid)
return 0;
return empireVotes[emp.index];
}
int getVoteFrom(Empire& emp, InfluenceCardSide side) const {
if(!emp.valid)
return 0;
int vote = empireVotes[emp.index];
if(side == ICS_Both)
return abs(vote);
if(vote < 0 && side == ICS_Oppose)
return -vote;
if(vote > 0 && side == ICS_Support)
return vote;
return 0;
}
uint countPlayed(const InfluenceCardType@ type, Empire@ matchEmpire = null, InfluenceCardSide matchSide = ICS_Both, const Targets@ matchTargets = null) const {
uint count = 0;
for(uint i = 0, cnt = events.length; i < cnt; ++i) {
auto evt = events[i];
if(evt.type != IVET_Card)
continue;
if(evt.cardEvent.card.type !is type)
continue;
if(matchEmpire !is null && evt.cardEvent.card.owner !is matchEmpire)
continue;
if(type.sideMode == ICS_Both && matchSide != ICS_Both) {
if(evt.cardEvent.targets.targets[type.sideTarget].side != (matchSide == ICS_Support))
continue;
}
if(matchTargets !is null && matchTargets != evt.cardEvent.targets)
continue;
count += 1;
}
return count;
}
bool hasActiveEffect(uint effId) const {
for(uint i = 0, cnt = effects.length; i < cnt; ++i) {
if(effects[i].type.id == effId)
return true;
}
return false;
}
#section server
void vote(Empire& emp, int amount) {
if(!emp.valid)
return;
empireVotes[emp.index] += amount;
if(amount > 0)
totalFor += amount;
else if(amount < 0)
totalAgainst -= amount;
for(uint i = 0, cnt = offers.length; i < cnt; ++i) {
if(offers[i].side == amount > 0)
offers[i].claims[emp.index] += abs(amount);
}
delta = true;
}
void extendTo(double minimum) {
if(minimumRemaining >= minimum)
return;
double posTime = (config::INFLUENCE_PASS_THRES - minimum) / positiveSpeed;
double negTime = (config::INFLUENCE_FAIL_THRES + minimum) / negativeSpeed;
currentTime = clamp(currentTime, negTime, posTime);
}
void leave(Empire& emp) {
if(!emp.valid)
return;
empiresPresent &= ~emp.mask;
int vote = empireVotes[emp.index];
if(vote > 0)
totalFor -= vote;
else if(vote < 0)
totalAgainst += vote;
empireVotes[emp.index] = 0;
delta = true;
}
bool makeOffer(InfluenceVoteOffer@ offer) {
if(!offer.canOffer())
return false;
if(!offer.take())
return false;
offer.id = nextOfferId++;
offers.insertLast(offer);
addOfferEvent(IVET_MakeOffer, offer.fromEmpire, offer);
delta = true;
return true;
}
bool claimOffer(Empire@ emp, int id) {
if(emp is null || !emp.valid)
return false;
InfluenceVoteOffer@ off;
for(uint i = 0, cnt = offers.length; i < cnt; ++i) {
if(offers[i].id == id) {
@off = offers[i];
break;
}
}
if(off is null)
return false;
if(off.claims[emp.index] < off.support)
return false;
if(off.fromEmpire is emp)
return false;
addOfferEvent(IVET_ClaimOffer, emp, off);
off.give(emp);
int maxRemaining = off.claims[emp.index] - off.support;
int found = -1;
for(uint i = 0, cnt = offers.length; i < cnt; ++i) {
if(offers[i] is off) {
found = int(i);
continue;
}
if(found == -1)
offers[i].claims[emp.index] -= off.support;
else
offers[i].claims[emp.index] = min(maxRemaining, offers[i].claims[emp.index]);
}
offers.removeAt(uint(found));
delta = true;
return true;
}
void addEvent(InfluenceVoteEvent@ evt) {
if(evt.visionMask == 0)
evt.visionMask = empiresPresent;
events.insertLast(evt);
delta = true;
//Inform cards
for(uint i = 0, cnt = effects.length; i < cnt; ++i)
effects[i].voteEvent(this, evt);
//Make notification
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
Empire@ emp = getEmpire(i);
if(!emp.major)
continue;
if(empiresPresent & emp.mask == 0)
continue;
emp.notifyVote(id, events.length - 1);
}
}
//Add a card event to the log, the card's state is stored for future formatting purposes.
InfluenceVoteEvent@ addCardEvent(InfluenceCard@ card, Targets@ targets, int weight, uint eventType = 0, uint visionMask = ~0) {
InfluenceVoteEvent evt(IVET_Card, card.owner);
@evt.cardEvent = InfluenceCardPlayEvent(card, targets);
evt.weight = weight;
evt.visionMask = visionMask;
evt.eventType = eventType;
addEvent(evt);
return evt;
}
//Add an offer event to the log
InfluenceVoteEvent@ addOfferEvent(InfluenceVoteEventType type, Empire@ empire, InfluenceVoteOffer@ offer) {
InfluenceVoteEvent evt(type, empire);
@evt.offer = InfluenceVoteOffer();
evt.offer = offer;
evt.offer.memo();
addEvent(evt);
return evt;
}
//Add a card effect to the vote, the effect will be informed of things happening to the vote.
// The current state of the card is copied over into the new effect.
InfluenceCard@ addCardEffect(InfluenceCard@ card, Targets@ targets) {
InfluenceCard effect = card;
effect.targets = targets;
effects.insertLast(effect);
delta = true;
return effect;
}
void removeCardEffect(InfluenceCard@ effect) {
effects.remove(effect);
delta = true;
}
//Hooks
void start(Targets@ targets, Empire@ byEmpire, InfluenceCard@ fromCard = null) {
@startedBy = byEmpire;
targets = targets;
startedAt = gameTime;
active = true;
delta = true;
vote(byEmpire, 1 + int(byEmpire.VoteStartSupport));
if(active) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onStart(this);
}
if(fromCard !is null) {
auto@ evt = addCardEvent(fromCard, fromCard.targets, 0);
evt.type = IVET_Start;
}
else
addEvent(InfluenceVoteEvent(IVET_Start, startedBy));
}
void tick(double time) {
//Handle timer update
if(totalFor > totalAgainst)
currentTime += time * positiveSpeed;
else
currentTime -= time * negativeSpeed;
//Tick all the hooks
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(type.hooks[i].onTick(this, time))
delta = true;
}
if(!active)
return;
//Tick cards
for(uint i = 0, cnt = effects.length; i < cnt; ++i)
effects[i].voteTick(this, time);
//Check offers
for(uint i = 0, cnt = offers.length; i < cnt; ++i) {
if(!offers[i].valid) {
offers[i].invalidate();
offers.removeAt(i);
--i; --cnt;
}
}
//Tally contribution points
for(uint i = 0, cnt = contribPoints.length; i < cnt; ++i)
contribPoints[i] += double(empireVotes[i]) * time;
//Figure out if we should end
if(currentTime <= config::INFLUENCE_FAIL_THRES)
end(false, false);
else if(currentTime >= config::INFLUENCE_PASS_THRES)
end(true, false);
}
void withdraw() {
addEvent(InfluenceVoteEvent(IVET_Withdraw, startedBy));
for(uint i = 0, cnt = offers.length; i < cnt; ++i) {
if(!offers[i].side) {
offers[i].give(startedBy);
offers.removeAt(i);
--i; --cnt;
}
}
end(false, true);
}
void end(bool passed, bool withdrawn) {
delta = true;
active = false;
endedAt = gameTime;
succeeded = passed;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onEnd(this, passed, withdrawn);
for(uint i = 0, cnt = effects.length; i < cnt; ++i)
effects[i].voteEnd(this, passed, withdrawn);
if(passed) {
currentTime = config::INFLUENCE_PASS_THRES;
addEvent(InfluenceVoteEvent(IVET_Pass));
}
else {
currentTime = config::INFLUENCE_FAIL_THRES;
addEvent(InfluenceVoteEvent(IVET_Fail));
}
for(uint i = 0, cnt = offers.length; i < cnt; ++i)
offers[i].expire();
offers.length = 0;
}
#section shadow
void tick(double time) {
//Handle timer update
if(totalFor > totalAgainst)
currentTime += time;
else
currentTime -= time;
}
#section all
//Networking
InfluenceVote(Message& msg) {
read(msg);
}
void write(Message& msg) {
msg << type.id;
targets.writeData(msg, type.targets);
msg << id;
msg << startedBy;
msg << startedAt;
msg << endedAt;
msg << currentTime;
msg << active;
msg << succeeded;
msg << empiresPresent;
msg << totalFor;
msg << totalAgainst;
msg << positiveSpeed;
msg << negativeSpeed;
msg << positiveCostPenalty;
msg << negativeCostPenalty;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
msg << empireVotes[i];
msg.writeBit(contribPoints[i] != 0);
if(contribPoints[i] != 0)
msg << float(contribPoints[i]);
}
uint cnt = offers.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i)
msg << offers[i];
msg << nextOfferId;
cnt = effects.length;
msg.writeSmall(cnt);
for(uint i = 0; i < cnt; ++i)
msg << effects[i];
writeEvents(msg, null);
}
void write(Message& msg, Empire@ forEmpire) {
write(msg);
writeEvents(msg, forEmpire);
}
void writeEvents(Message& msg, Empire@ forEmpire) {
msg.writeAlign();
uint pos = msg.reserve();
uint n = 0;
for(uint i = 0, cnt = events.length; i < cnt; ++i) {
if(!events[i].visibleTo(forEmpire))
continue;
msg << events[i];
++n;
}
msg.fill(pos, n);
}
void read(Message& msg) {
uint typeId = 0;
msg >> typeId;
@type = getInfluenceVoteType(typeId);
targets.readData(msg, type.targets);
data.length = type.hooks.length;
msg >> id;
msg >> startedBy;
msg >> startedAt;
msg >> endedAt;
msg >> currentTime;
msg >> active;
msg >> succeeded;
msg >> empiresPresent;
msg >> totalFor;
msg >> totalAgainst;
msg >> positiveSpeed;
msg >> negativeSpeed;
msg >> positiveCostPenalty;
msg >> negativeCostPenalty;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
msg >> empireVotes[i];
if(msg.readBit())
contribPoints[i] = msg.read_float();
else
contribPoints[i] = 0;
}
uint cnt = 0;
msg >> cnt;
offers.length = cnt;
for(uint i = 0; i < cnt; ++i) {
if(offers[i] is null)
@offers[i] = InfluenceVoteOffer();
msg >> offers[i];
}
msg >> nextOfferId;
cnt = msg.readSmall();
effects.length = cnt;
for(uint i = 0; i < cnt; ++i) {
if(effects[i] is null)
@effects[i] = InfluenceCard();
msg >> effects[i];
}
msg.readAlign();
uint evCnt = 0;
msg >> evCnt;
events.length = evCnt;
for(uint i = 0; i < evCnt; ++i) {
if(events[i] !is null)
events[i].read(msg);
else
@events[i] = InfluenceVoteEvent(msg);
}
}
//Saving
InfluenceVote(SaveFile& file) {
load(file);
}
void save(SaveFile& file) {
file.writeIdentifier(SI_InfluenceVote, type.id);
targets.saveData(file, type.targets);
file << id;
file << startedBy;
file << startedAt;
file << endedAt;
file << currentTime;
file << active;
file << succeeded;
file << empiresPresent;
file << totalFor;
file << totalAgainst;
file << positiveSpeed;
file << negativeSpeed;
file << positiveCostPenalty;
file << negativeCostPenalty;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
file << empireVotes[i];
file << contribPoints[i];
}
uint cnt = events.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << events[i];
cnt = effects.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << effects[i];
cnt = offers.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << offers[i];
file << nextOfferId;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].save(this, file);
}
void load(SaveFile& file) {
uint typeId = file.readIdentifier(SI_InfluenceVote);
@type = getInfluenceVoteType(typeId);
data.length = type.hooks.length;
targets.loadData(file, type.targets);
file >> id;
file >> startedBy;
file >> startedAt;
file >> endedAt;
file >> currentTime;
file >> active;
file >> succeeded;
file >> empiresPresent;
file >> totalFor;
file >> totalAgainst;
if(file >= SV_0080) {
file >> positiveSpeed;
file >> negativeSpeed;
file >> positiveCostPenalty;
file >> negativeCostPenalty;
}
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
file >> empireVotes[i];
if(file >= SV_0099)
file >> contribPoints[i];
}
uint cnt = 0;
file >> cnt;
events.length = cnt;
for(uint i = 0; i < cnt; ++i)
@events[i] = InfluenceVoteEvent(file);
file >> cnt;
effects.length = cnt;
for(uint i = 0; i < cnt; ++i)
@effects[i] = InfluenceCard(file);
if(file >= SV_0055) {
file >> cnt;
offers.length = cnt;
for(uint i = 0; i < cnt; ++i) {
if(offers[i] is null)
@offers[i] = InfluenceVoteOffer();
file >> offers[i];
}
file >> nextOfferId;
}
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].load(this, file);
}
//Formatting
string formatTitle() const {
return targets.format(type.name);
}
string formatDescription() const {
return targets.format(type.description);
}
};
//}}}
//{{{ Effects
class InfluenceVoteEffect : Hook {
uint hookIndex = 0;
void init(InfluenceVoteType@ type) {
}
//When the vote is started
void onStart(InfluenceVote@ vote) const {
}
//Every time the vote ticks
bool onTick(InfluenceVote@ vote, double time) const {
return false;
}
//Right after the vote ends
void onEnd(InfluenceVote@ vote, bool passed, bool withdrawn) const {
}
//Whether an empire can add weight for a side
bool canVote(const InfluenceVote@ vote, Empire& emp, bool support) const {
return true;
}
//Whether a card can be played
bool canPlayOn(const InfluenceVote@ vote, const InfluenceCard@ card, const Targets@ targets, InfluenceCardSide side) const {
return true;
}
//Save hook data
void save(InfluenceVote@ vote, SaveFile& file) const {
}
//Load hook data
void load(InfluenceVote@ vote, SaveFile& file) const {
}
};
//}}}
//{{{ Events
enum InfluenceVoteEventType {
IVET_Start,
IVET_Leave,
IVET_Card,
IVET_Withdraw,
IVET_Pass,
IVET_Fail,
IVET_Message,
IVET_MakeOffer,
IVET_ClaimOffer,
IVET_COUNT
};
tidy final class InfluenceVoteStub : Serializable, Savable {
uint id = uint(-1);
const InfluenceVoteType@ type;
Empire@ startedBy;
Targets targets;
InfluenceVoteStub() {
}
InfluenceVoteStub(const InfluenceVote@ vote) {
id = vote.id;
@type = vote.type;
@startedBy = vote.startedBy;
targets.set(vote.targets);
}
InfluenceVoteStub(Message& msg) {
read(msg);
}
void write(Message& msg) {
msg << type.id;
msg << startedBy;
msg << id;
msg << targets;
}
void read(Message& msg) {
uint typeId = 0;
msg >> typeId;
@type = getInfluenceVoteType(typeId);
msg >> startedBy;
msg >> id;
targets.read(msg);
}
InfluenceVoteStub(SaveFile& file) {
load(file);
}
void save(SaveFile& file) {
file.writeIdentifier(SI_InfluenceVote, type.id);
file << startedBy;
file << id;
file << targets;
}
void load(SaveFile& file) {
@type = getInfluenceVoteType(file.readIdentifier(SI_InfluenceVote));
file >> startedBy;
file >> id;
targets.load(file);
}
string formatTitle() const{
return targets.format(type.name);
}
};
tidy final class InfluenceVoteEvent : Serializable, Savable {
InfluenceVoteEventType type = IVET_COUNT;
Empire@ emp;
string text;
double time;
uint eventType = 0;
int weight = 0;
uint visionMask = 0;
InfluenceCardPlayEvent@ cardEvent;
InfluenceVoteOffer@ offer;
InfluenceVoteEvent() {
}
InfluenceVoteEvent(InfluenceVoteEventType Type, Empire@ empire = null) {
type = Type;
@emp = empire;
time = gameTime;
}
//Formatting
string formatEvent() const {
switch(type) {
case IVET_Start:
if(cardEvent !is null)
return format(locale::VOTE_LOG_START_CARD, formatEmpireName(emp, contactCheck=playerEmpire),
cardEvent.card.formatTitle(), toString(cardEvent.card.type.color));
else
return format(locale::VOTE_LOG_START, formatEmpireName(emp, contactCheck=playerEmpire));
case IVET_Leave:
return format(locale::VOTE_LOG_LEAVE, formatEmpireName(emp, contactCheck=playerEmpire));
case IVET_Withdraw:
return format(locale::VOTE_LOG_WITHDRAW, formatEmpireName(emp, contactCheck=playerEmpire));
case IVET_Pass:
return locale::VOTE_LOG_SUCCESS;
case IVET_Fail:
return locale::VOTE_LOG_FAIL;
case IVET_Message:
return format(locale::VOTE_LOG_MESSAGE, formatEmpireName(emp, contactCheck=playerEmpire), text);
case IVET_MakeOffer:
if(offer is null)
return "----";
return format(offer.side ? locale::LOG_OFFER_FOR : locale::LOG_OFFER_AGAINST,
formatEmpireName(emp, contactCheck=playerEmpire), offer.blurb, toString(offer.support));
case IVET_ClaimOffer:
if(offer is null)
return "----";
return format(offer.side ? locale::LOG_CLAIM_OFFER_FOR : locale::LOG_CLAIM_OFFER_AGAINST,
formatEmpireName(emp, contactCheck=playerEmpire), offer.blurb, toString(offer.support), formatEmpireName(offer.fromEmpire, contactCheck=playerEmpire));
case IVET_Card: {
string text;
for(uint i = 0, cnt = cardEvent.card.type.hooks.length; i < cnt; ++i) {
if(cardEvent.card.type.hooks[i].formatEvent(cardEvent.card, this, text))
return text;
}
return text;
}
}
return "";
}
bool visibleTo(Empire@ emp) const {
if(emp is null || !emp.valid)
return true;
return visionMask & emp.mask != 0;
}
bool isActiveEffect(const InfluenceVote@ vote = null) const {
if(cardEvent is null || cardEvent.card is null)
return false;
for(uint i = 0, cnt = cardEvent.card.type.hooks.length; i < cnt; ++i) {
if(cardEvent.card.type.hooks[i].isActiveEffect(cardEvent.card, this, vote))
return true;
}
return false;
}
InfluenceCardSide get_playedSide() const {
if(type == IVET_Card) {
if(cardEvent.card.type.sideMode == ICS_Both) {
auto@ targ = cardEvent.targets[cardEvent.card.type.sideTarget];
if(targ is null) {
if(weight < 0)
return ICS_Oppose;
if(weight > 0)
return ICS_Support;
return ICS_Neutral;
}
else {
return targ.side ? ICS_Support : ICS_Oppose;
}
}
else {
return cardEvent.card.type.sideMode;
}
}
else {
if(weight < 0)
return ICS_Oppose;
if(weight > 0)
return ICS_Support;
return ICS_Neutral;
}
}
//Network
InfluenceVoteEvent(Message& msg) {
read(msg);
}
void write(Message& msg) {
uint Type = type;
msg << Type;
msg << emp;
msg << text;
msg << time;
msg << eventType;
msg << weight;
msg << visionMask;
if(cardEvent !is null) {
msg.write1();
msg << cardEvent;
}
else {
msg.write0();
}
if(offer !is null) {
msg.write1();
msg << offer;
}
else {
msg.write0();
}
}
void read(Message& msg) {
uint Type = 0;
msg >> Type;
type = InfluenceVoteEventType(Type);
msg >> emp;
msg >> text;
msg >> time;
msg >> eventType;
msg >> weight;
msg >> visionMask;
if(msg.readBit()) {
if(cardEvent !is null)
cardEvent.read(msg);
else
@cardEvent = InfluenceCardPlayEvent(msg);
}
if(msg.readBit()) {
if(offer is null)
@offer = InfluenceVoteOffer();
msg >> offer;
}
}
//Saving
InfluenceVoteEvent(SaveFile& file) {
load(file);
}
void save(SaveFile& file) {
uint Type = type;
file << Type;
file << emp;
file << text;
file << time;
file << eventType;
file << weight;
file << visionMask;
if(cardEvent !is null) {
file.write1();
file << cardEvent;
}
else {
file.write0();
}
if(offer !is null) {
file.write1();
file << offer;
}
else {
file.write0();
}
}
void load(SaveFile& file) {
uint Type = 0;
file >> Type;
type = InfluenceVoteEventType(Type);
file >> emp;
file >> text;
file >> time;
file >> eventType;
file >> weight;
file >> visionMask;
bool loadCard = false;
if(file >= SV_0042)
loadCard = file.readBit();
else
loadCard = type == IVET_Card;
if(loadCard) {
if(cardEvent !is null)
cardEvent.load(file);
else
@cardEvent = InfluenceCardPlayEvent(file);
}
if(file >= SV_0055) {
if(file.readBit()) {
if(offer is null)
@offer = InfluenceVoteOffer();
file >> offer;
}
}
}
};
//}}}
//{{{ Initialization
array<InfluenceVoteType@> voteList;
dictionary voteIdents;
uint getInfluenceVoteTypeCount() {
return voteList.length;
}
const InfluenceVoteType@ getInfluenceVoteType(uint id) {
if(id >= voteList.length)
return null;
return voteList[id];
}
const InfluenceVoteType@ getInfluenceVoteType(const string& ident) {
InfluenceVoteType@ type;
voteIdents.get(ident, @type);
return type;
}
void addInfluenceVoteType(InfluenceVoteType@ type) {
type.id = voteList.length;
voteIdents.set(type.ident, @type);
voteList.insertLast(type);
}
int getInfluenceVoteID(const string& ident) {
InfluenceVoteType@ type;
voteIdents.get(ident, @type);
if(type is null)
return -1;
return type.id;
}
string getInfluenceVoteIdent(int id) {
if(uint(id) >= voteList.length)
return "";
return voteList[id].ident;
}
void readVoteLine(const string& line, InfluenceVoteType@ type, ReadFile@ file) {
auto@ hook = cast<InfluenceVoteEffect>(parseHook(line, "vote_effects::", instantiate=false, file=file));
if(hook !is null) {
hook.hookIndex = type.hooks.length;
type.hooks.insertLast(hook);
}
}
bool readVote(ReadFile@ file) {
InfluenceVoteType@ type;
do {
if(file.fullLine) {
if(type !is null)
readVoteLine(file.line, type, file);
else
file.error("Unexpected line: "+escape(file.line));
}
else if(file.key == "Vote") {
if(type !is null)
addInfluenceVoteType(type);
@type = InfluenceVoteType();
type.ident = file.value;
}
else if(file.key == "Name") {
type.name = localize(file.value);
}
else if(file.key == "Description") {
type.description = localize(file.value);
}
else if(file.key == "Color") {
type.color = toColor(file.value);
}
else if(file.key == "Icon") {
type.icon = getSprite(file.value);
}
else if(file.key == "Target") {
parseTarget(type.targets, file.value);
}
else if(file.key == "AI") {
auto@ hook = parseHook(file.value, "ai.diplomacy::", instantiate=false, file=file);
if(hook !is null)
type.ai.insertLast(hook);
}
else {
if(file.line.findFirst("(") != -1) {
if(type !is null)
readVoteLine(file.line, type, file);
else
file.error("Unexpected line: "+escape(file.line));
}
else {
if(type !is null)
addInfluenceVoteType(type);
return false;
}
}
} while(file++);
if(type !is null)
addInfluenceVoteType(type);
return true;
}
//}}}
// }}}
// {{{ Influence effects
export InfluenceEffectType;
export InfluenceEffect;
export InfluenceEffectEffect;
export getInfluenceEffectTypeCount;
export getInfluenceEffectType;
//{{{ Types
tidy final class InfluenceEffectType {
uint id = 0;
string ident;
string name;
string description;
Sprite icon;
Color color;
double reservation = 0.0;
double defaultDuration = -1.0;
bool dismissAble = true;
bool dismissNeedOwner = true;
Targets targets;
array<string> tags;
array<IInfluenceEffectEffect@> hooks;
void init() {
for(uint i = 0, cnt = hooks.length; i < cnt; ++i) {
auto@ hook = cast<Hook>(hooks[i]);
if(!hook.instantiate())
error("Could not instantiate hook: "+addrstr(hook)+" in "+ident);
hook.initTargets(targets);
hooks[i].init(this);
}
}
bool hasTag(const string& tag) const {
return tags.find(tag) != -1;
}
};
//}}}
//{{{ Effects
tidy final class InfluenceEffect : Serializable, Savable {
const InfluenceEffectType@ type;
Targets targets;
int id = -1;
int reserveId = -1;
array<any> data;
Empire@ owner;
double remainingTime = -1.0;
bool active = false;
bool delta = false;
InfluenceEffect() {
}
InfluenceEffect(const InfluenceEffectType@ Type) {
set(Type);
}
void set(const InfluenceEffectType@ Type) {
@type = Type;
targets.set(type.targets);
data.length = type.hooks.length;
}
bool checkTargets(const Targets@ check) const {
if(check is null)
return true;
if(check.targets.length != type.targets.targets.length)
return false;
for(uint i = 0, cnt = check.targets.length; i < cnt; ++i) {
if(!check.targets[i].filled)
return false;
if(check.targets[i].type != type.targets.targets[i].type)
return false;
}
return true;
}
bool canDismiss(Empire@ byEmpire) const {
if(!type.dismissAble)
return false;
if(type.dismissNeedOwner) {
if(byEmpire !is owner)
return false;
}
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(!type.hooks[i].canDismiss(this, byEmpire))
return false;
}
return true;
}
#section server
void start(Targets@ targets, Empire@ forEmpire, double duration = 0.0) {
@owner = forEmpire;
targets = targets;
if(duration == 0)
remainingTime = type.defaultDuration;
else
remainingTime = duration;
active = true;
delta = true;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onStart(this);
if(type.reservation != 0.0 && owner !is null)
reserveId = owner.reserveInfluence(type.reservation);
}
void tick(double time) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i) {
if(type.hooks[i].onTick(this, time))
delta = true;
}
if(active && remainingTime >= 0) {
remainingTime -= time;
if(remainingTime < 0) {
remainingTime = 0.0;
end();
}
}
}
void dismiss(Empire@ byEmpire) {
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onDismiss(this, byEmpire);
end();
}
void end() {
active = false;
delta = true;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].onEnd(this);
if(reserveId != -1 && owner !is null)
owner.removeInfluenceReservation(reserveId);
}
#section shadow
void tick(double time) {
if(remainingTime >= 0)
remainingTime -= time;
}
#section all
//Networking
InfluenceEffect(Message& msg) {
read(msg);
}
void write(Message& msg) {
msg << type.id;
targets.writeData(msg, type.targets);
msg << id;
msg << owner;
msg << remainingTime;
msg << active;
}
void read(Message& msg) {
uint typeId = 0;
msg >> typeId;
@type = getInfluenceEffectType(typeId);
targets.readData(msg, type.targets);
data.length = type.hooks.length;
msg >> id;
msg >> owner;
msg >> remainingTime;
msg >> active;
}
//Saving
InfluenceEffect(SaveFile& file) {
load(file);
}
void save(SaveFile& file) {
file.writeIdentifier(SI_InfluenceEffect, type.id);
targets.saveData(file, type.targets);
file << id;
file << owner;
file << reserveId;
file << remainingTime;
file << active;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].save(this, file);
}
void load(SaveFile& file) {
uint typeId = 0;
if(file == SV_0063)
file >> typeId;
else
typeId = file.readIdentifier(SI_InfluenceEffect);
@type = getInfluenceEffectType(typeId);
data.length = type.hooks.length;
targets.loadData(file, type.targets);
file >> id;
file >> owner;
file >> reserveId;
file >> remainingTime;
file >> active;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].load(this, file);
}
//Formatting
string formatTitle() const {
return targets.format(type.name);
}
string formatDescription() const {
return targets.format(type.description);
}
};
//}}}
//{{{ Effects
interface IInfluenceEffectEffect {
void set_dataIndex(uint ind);
void init(InfluenceEffectType@ type);
void onStart(InfluenceEffect@ effect) const;
bool onTick(InfluenceEffect@ effect, double time) const;
void onDismiss(InfluenceEffect@ effect, Empire@ byEmpire) const;
void onEnd(InfluenceEffect@ effect) const;
bool canDismiss(const InfluenceEffect@ effect, Empire@ byEmpire) const;
void save(InfluenceEffect@ effect, SaveFile& file) const;
void load(InfluenceEffect@ effect, SaveFile& file) const;
};
class InfluenceEffectEffect : Hook, IInfluenceEffectEffect {
uint hookIndex = 0;
void set_dataIndex(uint ind) {
hookIndex = ind;
}
void init(InfluenceEffectType@ type) {
}
void onStart(InfluenceEffect@ effect) const {
}
bool onTick(InfluenceEffect@ effect, double time) const {
return false;
}
void onDismiss(InfluenceEffect@ effect, Empire@ byEmpire) const {
}
void onEnd(InfluenceEffect@ effect) const {
}
bool canDismiss(const InfluenceEffect@ effect, Empire@ byEmpire) const {
return true;
}
void save(InfluenceEffect@ effect, SaveFile& file) const {
}
void load(InfluenceEffect@ effect, SaveFile& file) const {
}
};
//}}}
//{{{ Initialization
array<InfluenceEffectType@> effectList;
dictionary effectIdents;
uint getInfluenceEffectTypeCount() {
return effectList.length;
}
const InfluenceEffectType@ getInfluenceEffectType(uint id) {
if(id >= effectList.length)
return null;
return effectList[id];
}
const InfluenceEffectType@ getInfluenceEffectType(const string& ident) {
InfluenceEffectType@ type;
effectIdents.get(ident, @type);
return type;
}
int getInfluenceEffectID(const string& ident) {
InfluenceEffectType@ type;
effectIdents.get(ident, @type);
if(type is null)
return -1;
return type.id;
}
string getInfluenceEffectIdent(int id) {
if(uint(id) >= effectList.length)
return "";
return effectList[id].ident;
}
void addInfluenceEffectType(InfluenceEffectType@ type) {
type.id = effectList.length;
effectIdents.set(type.ident, @type);
effectList.insertLast(type);
}
void readEffectLine(const string& line, InfluenceEffectType@ type, ReadFile@ file) {
auto@ hook = cast<IInfluenceEffectEffect>(parseHook(line, "influence_effects::", instantiate=false, file=file));
if(hook !is null) {
hook.dataIndex = type.hooks.length;
type.hooks.insertLast(hook);
}
}
bool readEffect(ReadFile@ file) {
InfluenceEffectType@ type;
do {
if(file.fullLine) {
if(type !is null)
readEffectLine(file.line, type, file);
else
file.error("Unexpected line: "+escape(file.line));
}
else if(file.key == "Effect") {
if(type !is null)
addInfluenceEffectType(type);
@type = InfluenceEffectType();
type.ident = file.value;
}
else if(file.key == "Name") {
type.name = localize(file.value);
}
else if(file.key == "Description") {
type.description = localize(file.value);
}
else if(file.key == "Color") {
type.color = toColor(file.value);
}
else if(file.key == "Icon") {
type.icon = getSprite(file.value);
}
else if(file.key == "Target") {
parseTarget(type.targets, file.value);
}
else if(file.key == "Default Duration") {
type.defaultDuration = toDouble(file.value);
}
else if(file.key == "Upkeep") {
type.reservation = toDouble(file.value);
}
else if(file.key == "Tag") {
type.tags.insertLast(file.value);
}
else if(file.key == "Dismissable") {
type.dismissAble = toBool(file.value);
}
else if(file.key == "Dismiss Needs Owner") {
type.dismissNeedOwner = toBool(file.value);
}
else {
if(file.line.findFirst("(") != -1) {
if(type !is null)
readEffectLine(file.line, type, file);
else
file.error("Unexpected line: "+escape(file.line));
}
else {
if(type !is null)
addInfluenceEffectType(type);
return false;
}
}
} while(file++);
if(type !is null)
addInfluenceEffectType(type);
return true;
}
//}}}
// }}}
// {{{ Treaties
export InfluenceClauseType;
export getInfluenceClauseType, getInfluenceClauseTypeCount;
export Treaty, Clause;
//{{{ Clause Types
tidy final class InfluenceClauseType {
uint id = 0;
string ident;
string name;
string description;
Sprite icon;
Color color;
bool freeClause = false;
bool teamClause = false;
bool defaultClause = false;
array<InfluenceClauseHook@> hooks;
void init() {
for(uint i = 0, cnt = hooks.length; i < cnt; ++i) {
if(!hooks[i].instantiate())
error("Could not instantiate hook: "+addrstr(hooks[i])+" in "+ident);
}
}
};
class InfluenceClauseHook : Hook {
uint hookIndex = 0;
void onStart(Treaty@ treaty, Clause@ clause) const {}
void onTick(Treaty@ treaty, Clause@ clause, double time) const {}
void onEnd(Treaty@ treaty, Clause@ clause) const {}
void onJoin(Treaty@ treaty, Clause@ clause, Empire@ joined) const {}
void onLeave(Treaty@ treaty, Clause@ clause, Empire@ left) const {}
bool isVisibleTo(const Treaty@ treaty, const Clause@ clause, Empire& empire) const { return true; }
bool canLeave(const Treaty@ treaty, const Clause@ clause, Empire& empire) const { return true; }
bool canInvite(const Treaty@ treaty, const Clause@ clause, Empire& from, Empire& invite) const { return true; }
void save(Clause@ clause, SaveFile& file) const {}
void load(Clause@ clause, SaveFile& file) const {}
};
//}}}
//{{{ Treaties
enum TreatyEventType {
TET_Invite,
TET_Leave,
TET_Dismiss,
TET_Join,
TET_Decline,
TET_Subjugate
};
tidy final class Treaty : Serializable, Savable {
uint id = 0;
string name;
array<Clause@> clauses;
uint visibleMask = uint(~0);
uint inviteMask = 0;
uint presentMask = 0;
Empire@ leader;
array<Empire@> joinedEmpires;
bool started = false;
bool delta = false;
void write(Message& msg) {
msg.writeSmall(id);
msg << name;
msg.writeSmall(clauses.length);
for(uint i = 0, cnt = clauses.length; i < cnt; ++i)
msg << clauses[i];
msg.writeSignedSmall(visibleMask);
msg.writeSmall(inviteMask);
msg.writeSmall(presentMask);
msg << leader;
msg.writeSmall(joinedEmpires.length);
for(uint i = 0, cnt = joinedEmpires.length; i < cnt; ++i)
msg << joinedEmpires[i];
msg << started;
}
void read(Message& msg) {
id = msg.readSmall();
msg >> name;
clauses.length = msg.readSmall();
for(uint i = 0, cnt = clauses.length; i < cnt; ++i) {
if(clauses[i] is null)
@clauses[i] = Clause();
msg >> clauses[i];
}
visibleMask = msg.readSignedSmall();
inviteMask = msg.readSmall();
presentMask = msg.readSmall();
msg >> leader;
joinedEmpires.length = msg.readSmall();
for(uint i = 0, cnt = joinedEmpires.length; i < cnt; ++i)
msg >> joinedEmpires[i];
msg >> started;
}
void save(SaveFile& file) {
file << id;
file << name;
file << clauses.length;
for(uint i = 0, cnt = clauses.length; i < cnt; ++i)
file << clauses[i];
file << visibleMask << inviteMask << presentMask;
file << leader;
file << joinedEmpires.length;
for(uint i = 0, cnt = joinedEmpires.length; i < cnt; ++i)
file << joinedEmpires[i];
file << started;
}
void load(SaveFile& file) {
file >> id;
file >> name;
uint cnt = 0;
file >> cnt;
clauses.length = cnt;
for(uint i = 0; i < cnt; ++i) {
if(clauses[i] is null)
@clauses[i] = Clause();
file >> clauses[i];
}
file >> visibleMask >> inviteMask >> presentMask;
file >> leader;
file >> cnt;
joinedEmpires.length = cnt;
for(uint i = 0; i < cnt; ++i)
file >> joinedEmpires[i];
file >> started;
}
bool canLeave(Empire& emp) const {
if(joinedEmpires.length == 1 && presentMask & emp.mask != 0)
return true;
for(uint i = 0, cnt = clauses.length; i < cnt; ++i) {
auto@ clause = clauses[i];
for(uint n = 0, ncnt = clause.type.hooks.length; n < ncnt; ++n) {
if(!clause.type.hooks[n].canLeave(this, clause, emp))
return false;
}
}
return true;
}
bool canInvite(Empire& emp, Empire& invite) const {
if(leader !is null && leader !is emp)
return false;
if(presentMask != 0 && presentMask & emp.mask == 0)
return false;
if(inviteMask & invite.mask != 0)
return false;
if(presentMask & invite.mask != 0)
return false;
if(emp.SubjugatedBy !is null)
return false;
if(invite.SubjugatedBy !is null && (invite.SubjugatedBy !is leader || emp !is leader))
return false;
for(uint i = 0, cnt = clauses.length; i < cnt; ++i) {
auto@ clause = clauses[i];
for(uint n = 0, ncnt = clause.type.hooks.length; n < ncnt; ++n) {
if(!clause.type.hooks[n].canInvite(this, clause, emp, invite))
return false;
}
}
return true;
}
bool isVisibleTo(Empire@ emp) const {
if(emp is spectatorEmpire)
return true;
if(emp is null)
return false;
if(visibleMask & emp.mask == 0)
return false;
if(inviteMask & emp.mask != 0 || presentMask & emp.mask != 0)
return true;
if(leader !is null) {
if(emp.ContactMask & leader.mask == 0)
return false;
if(joinedEmpires.length <= 1)
return false;
}
else {
if(joinedEmpires.length <= 1)
return false;
for(uint i = 0, cnt = joinedEmpires.length; i < cnt; ++i) {
if(emp.ContactMask & joinedEmpires[i].mask == 0)
return false;
}
}
for(uint i = 0, cnt = clauses.length; i < cnt; ++i) {
auto@ clause = clauses[i];
for(uint n = 0, ncnt = clause.type.hooks.length; n < ncnt; ++n) {
if(!clause.type.hooks[n].isVisibleTo(this, clause, emp))
return false;
}
}
return true;
}
bool hasClause(const string& name) const {
for(uint i = 0, cnt = clauses.length; i < cnt; ++i) {
if(clauses[i].type.ident == name)
return true;
}
return false;
}
bool hasClause(const InfluenceClauseType@ type) const {
for(uint i = 0, cnt = clauses.length; i < cnt; ++i) {
if(clauses[i].type is type)
return true;
}
return false;
}
#section client
void addClause(const InfluenceClauseType@ type) {
Clause clause(type);
clauses.insertLast(clause);
}
string getTooltip() const {
string tt;
if(leader !is null)
tt += format("[font=Medium]$1's [b]$2[/b][/font]",
formatEmpireName(leader), name);
else
tt += format("[font=Medium][b]$1[/b][/font]", name);
tt += "\n"+locale::SIGNED_BY+" ";
for(uint i = 0, cnt = joinedEmpires.length; i < cnt; ++i) {
if(i != 0)
tt += ", ";
tt += formatEmpireName(joinedEmpires[i]);
}
if(inviteMask != 0) {
tt += "\n\n[b]"+locale::INVITED_TO+":\n[offset=30]";
bool haveInvites = false;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
auto@ other = getEmpire(i);
if(other.mask & inviteMask != 0) {
if(haveInvites)
tt += ", ";
tt += formatEmpireName(other);
haveInvites = true;
}
}
tt += "[/offset][/b]";
}
for(uint i = 0, cnt = clauses.length; i < cnt; ++i) {
tt += format("\n\n[b][img=$3;24/] $1[/b]\n$2",
clauses[i].type.name, clauses[i].type.description,
getSpriteDesc(clauses[i].type.icon));
}
return tt;
}
#section server
void init(Empire& starter) {
if(leader !is null)
@leader = starter;
presentMask = starter.mask;
visibleMask = uint(~0);
started = false;
delta = true;
}
void invite(Empire& inviter, Empire& emp) {
if(presentMask & emp.mask == 0) {
inviteMask |= emp.mask;
visibleMask |= emp.mask;
delta = true;
//Send invite to empire
emp.notifyTreaty(id, TET_Invite, inviter, emp);
//Send invite notification to other members
for(uint i = 0, cnt = joinedEmpires.length; i < cnt; ++i) {
auto@ other = joinedEmpires[i];
if(other is inviter || other is emp)
continue;
other.notifyTreaty(id, TET_Invite, emp);
}
}
}
void join(Empire& emp, bool force = false) {
if(!force && inviteMask & emp.mask == 0)
return;
delta = true;
inviteMask &= ~emp.mask;
presentMask |= emp.mask;
joinedEmpires.insertLast(emp);
if(started) {
for(uint i = 0, cnt = clauses.length; i < cnt; ++i) {
auto@ clause = clauses[i];
for(uint n = 0, ncnt = clause.type.hooks.length; n < ncnt; ++n) {
clause.type.hooks[n].onJoin(this, clause, emp);
}
}
}
else if(joinedEmpires.length == 2) {
start();
}
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
auto@ other = getEmpire(i);
if(!other.major || !isVisibleTo(other))
continue;
if(other is emp)
continue;
other.notifyTreaty(id, TET_Join, emp);
}
}
void leave(Empire& emp) {
delta = true;
inviteMask &= ~emp.mask;
if(joinedEmpires.find(emp) != -1) {
presentMask &= ~emp.mask;
joinedEmpires.remove(emp);
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
auto@ other = getEmpire(i);
if(!other.major || !isVisibleTo(other))
continue;
if(other is emp)
continue;
other.notifyTreaty(id, emp is leader ? TET_Dismiss : TET_Leave, emp);
}
if(started) {
for(uint i = 0, cnt = clauses.length; i < cnt; ++i) {
auto@ clause = clauses[i];
for(uint n = 0, ncnt = clause.type.hooks.length; n < ncnt; ++n) {
clause.type.hooks[n].onLeave(this, clause, emp);
}
}
}
}
}
void decline(Empire& emp) {
delta = true;
inviteMask &= ~emp.mask;
//Send decline message to members
for(uint i = 0, cnt = joinedEmpires.length; i < cnt; ++i) {
auto@ other = joinedEmpires[i];
if(other is emp)
continue;
other.notifyTreaty(id, TET_Decline, emp);
}
}
void start() {
for(uint i = 0, cnt = clauses.length; i < cnt; ++i) {
auto@ clause = clauses[i];
for(uint n = 0, ncnt = clause.type.hooks.length; n < ncnt; ++n) {
clause.type.hooks[n].onStart(this, clause);
}
}
started = true;
delta = true;
}
void tick(double time) {
if(started) {
for(uint i = 0, cnt = clauses.length; i < cnt; ++i) {
auto@ clause = clauses[i];
for(uint n = 0, ncnt = clause.type.hooks.length; n < ncnt; ++n) {
clause.type.hooks[n].onTick(this, clause, time);
}
}
}
}
void end() {
if(started) {
for(uint i = 0, cnt = clauses.length; i < cnt; ++i) {
auto@ clause = clauses[i];
for(uint n = 0, ncnt = clause.type.hooks.length; n < ncnt; ++n) {
clause.type.hooks[n].onEnd(this, clause);
}
}
}
started = false;
delta = true;
}
void addClause(const InfluenceClauseType@ type) {
if(type is null)
return;
auto@ clause = Clause(type);
if(started) {
for(uint n = 0, ncnt = clause.type.hooks.length; n < ncnt; ++n)
clause.type.hooks[n].onStart(this, clause);
}
clauses.insertLast(clause);
delta = true;
}
#section all
};
tidy final class Clause : Serializable, Savable {
const InfluenceClauseType@ type;
array<any> data;
Clause() {}
Clause(const InfluenceClauseType@ type) { set(type); }
void set(const InfluenceClauseType@ type) {
@this.type = type;
data.length = type.hooks.length;
}
void write(Message& msg) {
msg.writeSmall(type.id);
}
void read(Message& msg) {
@type = getInfluenceClauseType(msg.readSmall());
data.length = type.hooks.length;
}
void save(SaveFile& file) {
file.writeIdentifier(SI_InfluenceClause, type.id);
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].save(this, file);
}
void load(SaveFile& file) {
@type = getInfluenceClauseType(file.readIdentifier(SI_InfluenceClause));
data.length = type.hooks.length;
for(uint i = 0, cnt = type.hooks.length; i < cnt; ++i)
type.hooks[i].load(this, file);
}
};
//}}}
//{{{ Initialization
array<InfluenceClauseType@> clauseList;
dictionary clauseIdents;
uint getInfluenceClauseTypeCount() {
return clauseList.length;
}
const InfluenceClauseType@ getInfluenceClauseType(uint id) {
if(id >= clauseList.length)
return null;
return clauseList[id];
}
const InfluenceClauseType@ getInfluenceClauseType(const string& ident) {
InfluenceClauseType@ type;
clauseIdents.get(ident, @type);
return type;
}
void addInfluenceClauseType(InfluenceClauseType@ type) {
type.id = clauseList.length;
clauseIdents.set(type.ident, @type);
clauseList.insertLast(type);
}
void readClauseLine(const string& line, InfluenceClauseType@ type, ReadFile@ file) {
auto@ hook = cast<InfluenceClauseHook>(parseHook(line, "clause_effects::", instantiate=false, file=file));
if(hook !is null) {
hook.hookIndex = type.hooks.length;
type.hooks.insertLast(hook);
}
}
bool readClause(ReadFile@ file) {
InfluenceClauseType@ type;
do {
if(file.fullLine) {
if(type !is null)
readClauseLine(file.line, type, file);
else
file.error("Unexpected line: "+escape(file.line));
}
else if(file.key == "Clause") {
if(type !is null)
addInfluenceClauseType(type);
@type = InfluenceClauseType();
type.ident = file.value;
}
else if(file.key == "Name") {
type.name = localize(file.value);
}
else if(file.key == "Description") {
type.description = localize(file.value);
}
else if(file.key == "Color") {
type.color = toColor(file.value);
}
else if(file.key == "Icon") {
type.icon = getSprite(file.value);
}
else if(file.key == "Free Clause") {
type.freeClause = toBool(file.value);
}
else if(file.key == "Team Clause") {
type.teamClause = toBool(file.value);
}
else if(file.key == "Default Clause") {
type.defaultClause = toBool(file.value);
}
else {
if(file.line.findFirst("(") != -1) {
if(type !is null)
readClauseLine(file.line, type, file);
else
file.error("Unexpected line: "+escape(file.line));
}
else {
if(type !is null)
addInfluenceClauseType(type);
return false;
}
}
} while(file++);
if(type !is null)
addInfluenceClauseType(type);
return true;
}
//}}}
// }}}
// {{{ Offers
export DiplomacyOfferType, DiplomacyOffer;
export InfluenceVoteOffer;
enum DiplomacyOfferType {
DOT_Money,
DOT_Energy,
DOT_Card,
DOT_Fleet,
DOT_Planet,
DOT_Artifact,
DOT_COUNT,
DOT_INVALID = DOT_COUNT
};
tidy final class DiplomacyOffer : Serializable, Savable {
Empire@ bound;
DiplomacyOfferType type = DOT_INVALID;
double value = 0;
int id = -1;
Object@ obj;
InfluenceCard@ memoCard;
DiplomacyOffer() {
}
DiplomacyOffer(DiplomacyOfferType type, Object@ obj) {
this.type = type;
@this.obj = obj;
}
DiplomacyOffer(DiplomacyOfferType type, double value = 0.0, int id = 0) {
this.type = type;
this.value = value;
this.id = id;
}
void save(SaveFile& file) {
file << uint(type);
file << bound;
file << value;
file << obj;
file << id;
if(memoCard !is null) {
file.write1();
file << memoCard;
}
else {
file.write0();
}
}
void load(SaveFile& file) {
uint tp = 0;
file >> tp;
type = DiplomacyOfferType(tp);
file >> bound;
file >> value;
file >> obj;
file >> id;
if(file.readBit()) {
if(memoCard is null)
@memoCard = InfluenceCard();
file >> memoCard;
}
}
void write(Message& msg) {
msg.writeSmall(type);
msg << bound;
msg << value;
msg << obj;
msg << id;
if(memoCard !is null) {
msg.write1();
msg << memoCard;
}
else {
msg.write0();
}
}
void read(Message& msg) {
type = DiplomacyOfferType(msg.readSmall());
msg >> bound;
msg >> value;
msg >> obj;
msg >> id;
if(msg.readBit()) {
if(memoCard is null)
@memoCard = InfluenceCard();
msg >> memoCard;
}
}
void memo(Empire@ from) {
if(type == DOT_Card) {
@memoCard = InfluenceCard();
receive(from.getInfluenceCard(id), memoCard);
}
}
string get_blurb() const {
switch(type) {
case DOT_Money:
{
return format("[img=$3;20/] [b][color=$1]$2[/color][/b]",
toString(colors::Money), formatMoney(value),
getSpriteDesc(icons::Money));
}
case DOT_Energy:
{
return format("[img=$3;20/] [b][color=$1]$2[/color][/b]",
toString(colors::Energy), standardize(value, true),
getSpriteDesc(icons::Energy));
}
case DOT_Planet:
case DOT_Fleet:
case DOT_Artifact:
{
return format("[b]$1[/b]", formatObject(obj, showIcon=true));
}
case DOT_Card:
{
if(memoCard !is null) {
return memoCard.formatBlurb(int(value));
}
else if(bound !is null) {
InfluenceCard card;
if(receive(bound.getInfluenceCard(id), card))
return card.formatBlurb(int(value));
}
return "---";
}
}
return "";
}
bool canOffer(Empire@ from) {
switch(type) {
case DOT_Fleet: {
Ship@ ship = cast<Ship>(obj);
if(ship is null || !ship.valid)
return false;
if(!ship.hasLeaderAI || ship.owner !is from)
return false;
if(ship.blueprint.design.hasTag(ST_CannotDonate))
return false;
return true;
}
case DOT_Planet:
return obj !is null && obj.valid && obj.isPlanet && obj.owner is from;
case DOT_Money:
return from.canPay(value) && value <= INT_MAX/2 && value > 0;
case DOT_Energy:
return from.EnergyStored >= value && value > 0;
case DOT_Card:
return from.getInfluenceCardUses(id) >= int(value) && value > 0;
case DOT_Artifact:
{
if(obj is null || !obj.isArtifact)
return false;
Region@ region = obj.region;
return obj.valid && obj.owner !is null
&& (!obj.owner.valid || obj.owner is from)
&& region !is null && region.TradeMask & from.mask != 0;
}
}
return true;
}
bool conflicts(Empire@ from, const DiplomacyOffer& other) const {
if(type == DOT_Card && id == other.id)
return true;
return false;
}
#section server
bool take(Empire@ from) {
switch(type) {
case DOT_Money:
{
id = from.consumeBudget(value);
return id != -1;
}
case DOT_Energy:
{
double eng = from.consumeEnergy(value, consumePartial=false);
if(eng >= value - 0.001) {
from.modEnergyAllocated(value);
return true;
}
return false;
}
case DOT_Card:
{
@bound = from;
}
}
return true;
}
void reverse(Empire@ from, bool initial = false) {
switch(type) {
case DOT_Money:
from.refundBudget(value, id);
break;
case DOT_Energy:
from.modEnergyStored(value);
from.modEnergyAllocated(-value);
break;
}
}
bool valid(Empire@ from) {
switch(type) {
case DOT_Fleet: {
Ship@ ship = cast<Ship>(obj);
if(ship is null || !ship.valid)
return false;
if(!ship.hasLeaderAI || ship.owner !is from)
return false;
if(ship.blueprint.design.hasTag(ST_CannotDonate))
return false;
return true;
}
case DOT_Planet:
return obj !is null && obj.valid && obj.isPlanet && obj.owner is from;
case DOT_Card:
return from.getInfluenceCardUses(id) >= int(value);
case DOT_Artifact:
{
if(obj is null || !obj.isArtifact)
return false;
Region@ region = obj.region;
return obj.valid && obj.owner !is null
&& (!obj.owner.valid || obj.owner is from)
&& region !is null && region.TradeMask & from.mask != 0;
}
}
return true;
}
void delayMaintCost(Empire@ from, Empire@ to, int amount) {
if(amount <= 0)
return;
from.modForwardBudget(-amount);
to.modForwardBudget(amount);
}
bool give(Empire@ from, Empire@ to, bool delayMaintenance = false, bool preventFloat = false) {
switch(type) {
case DOT_Money:
{
if(preventFloat)
to.modRemainingBudget(value);
else
to.addBonusBudget(value);
return true;
}
case DOT_Energy:
{
double amt = value * to.EnergyEfficiency;
to.modEnergyStored(amt);
if(preventFloat) {
to.addFloatedEnergy(from, amt);
from.modEnergyAllocated(amt-value);
}
else
from.modEnergyAllocated(-value);
return true;
}
case DOT_Planet:
{
if(delayMaintenance)
delayMaintCost(from, to, -obj.income);
obj.takeoverPlanet(to);
return true;
}
case DOT_Fleet:
{
if(delayMaintenance)
delayMaintCost(from, to, cast<Ship>(obj).maintenanceCost);
obj.takeoverFleet(to, moveToTerritory=true);
return true;
}
case DOT_Card:
{
from.copyCardTo(id, to, int(value));
from.takeCardUse(id, int(value));
return true;
}
case DOT_Artifact:
{
if(obj.owner is from)
@obj.owner = to;
const SystemDesc@ targSys = getClosestSystem(obj.position, to);
if(targSys !is null) {
vec3d pos = targSys.position;
vec2d offset = random2d(150.0, targSys.radius - 150.0);
pos.x += offset.x;
pos.z += offset.y;
obj.orbitAround(pos, targSys.position);
}
}
}
return true;
}
#section all
};
tidy final class InfluenceVoteOffer : Serializable, Savable {
int id = 0;
Empire@ fromEmpire;
bool side = true;
array<DiplomacyOffer> offers;
array<int> claims(getEmpireCount(), 0);
int support;
string get_blurb() const {
string text;
for(uint i = 0, cnt = offers.length; i < cnt; ++i) {
if(i != 0)
text += ", ";
text += offers[i].blurb;
}
return text;
}
bool canOffer() {
if(offers.length == 0)
return false;
for(uint i = 0, cnt = offers.length; i < cnt; ++i) {
if(!offers[i].canOffer(fromEmpire))
return false;
for(uint j = 0; j < cnt; ++j) {
if(i == j)
continue;
if(offers[i].conflicts(fromEmpire, offers[j]))
return false;
}
}
return true;
}
#section server
bool take() {
for(uint i = 0, cnt = offers.length; i < cnt; ++i) {
if(!offers[i].take(fromEmpire)) {
for(uint j = 0; j < i; ++j)
offers[j].reverse(fromEmpire, initial=true);
return false;
}
}
return true;
}
void give(Empire@ emp) {
for(uint i = 0, cnt = offers.length; i < cnt; ++i)
offers[i].give(fromEmpire, emp);
}
void memo() {
for(uint i = 0, cnt = offers.length; i < cnt; ++i)
offers[i].memo(fromEmpire);
}
bool get_valid() {
for(uint i = 0, cnt = offers.length; i < cnt; ++i) {
if(!offers[i].valid(fromEmpire))
return false;
}
return true;
}
void invalidate() {
for(uint i = 0, cnt = offers.length; i < cnt; ++i) {
if(offers[i].valid(fromEmpire))
offers[i].reverse(fromEmpire);
}
}
void expire() {
for(uint i = 0, cnt = offers.length; i < cnt; ++i)
offers[i].reverse(fromEmpire);
}
#section all
void save(SaveFile& file) {
file << id;
file << fromEmpire;
file << side;
file << support;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i)
file << claims[i];
uint cnt = offers.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << offers[i];
}
void load(SaveFile& file) {
file >> id;
file >> fromEmpire;
file >> side;
file >> support;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i)
file >> claims[i];
uint cnt = 0;
file >> cnt;
offers.length = cnt;
for(uint i = 0; i < cnt; ++i)
file >> offers[i];
}
void write(Message& msg) {
msg.writeSmall(id);
msg << fromEmpire;
msg << side;
msg.writeSmall(support);
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i)
msg.writeSmall(claims[i]);
uint cnt = offers.length;
msg.writeSmall(cnt);
for(uint i = 0; i < cnt; ++i)
msg << offers[i];
}
void read(Message& msg) {
id = msg.readSmall();
msg >> fromEmpire;
msg >> side;
support = msg.readSmall();
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i)
claims[i] = msg.readSmall();
uint cnt = msg.readSmall();
offers.length = cnt;
for(uint i = 0; i < cnt; ++i)
msg >> offers[i];
}
};
//}}}
// {{{ Edits
export DiplomacyEdict, DiplomacyEdictType;
enum DiplomacyEdictType {
DET_None,
DET_Conquer,
};
tidy final class DiplomacyEdict : Savable, Serializable {
DiplomacyEdictType type = DET_None;
Empire@ empTarget;
Object@ objTarget;
int targetId = -1;
void clear() {
type = DET_None;
@empTarget = null;
@objTarget = null;
targetId = -1;
}
void write(Message& msg) {
msg.writeSmall(uint(type));
msg << empTarget;
msg << objTarget;
msg << targetId;
}
void read(Message& msg) {
type = DiplomacyEdictType(msg.readSmall());
msg >> empTarget;
msg >> objTarget;
msg >> targetId;
}
void save(SaveFile& file) {
file << uint(type);
file << empTarget;
file << objTarget;
file << targetId;
}
void load(SaveFile& file) {
uint t = 0;
file >> t;
type = DiplomacyEdictType(t);
file >> empTarget;
file >> objTarget;
file >> targetId;
}
};
//}}}
// {{{ Global initialization
void readFile(const string& filename) {
ReadFile file(filename, true);
while(file++) {
bool used = false;
while(!used) {
if(file.key == "Card") {
used = readCard(file);
}
else if(file.key == "Vote") {
used = readVote(file);
}
else if(file.key == "Effect") {
used = readEffect(file);
}
else if(file.key == "Clause") {
used = readClause(file);
}
else {
file.error("Invalid line outside of block: "+escape(file.line));
break;
}
}
}
}
void preInit() {
FileList list("data/influence", "*.txt", true);
for(uint i = 0, cnt = list.length; i < cnt; ++i)
readFile(list.path[i]);
}
void init() {
for(uint i = 0, cnt = cardList.length; i < cnt; i++)
cardList[i].init();
for(uint i = 0, cnt = voteList.length; i < cnt; i++)
voteList[i].init();
for(uint i = 0, cnt = effectList.length; i < cnt; i++)
effectList[i].init();
for(uint i = 0, cnt = clauseList.length; i < cnt; i++)
clauseList[i].init();
}
void saveIdentifiers(SaveFile& file) {
//Influence Cards
for(uint i = 0, cnt = cardList.length; i < cnt; ++i) {
auto@ type = cardList[i];
file.addIdentifier(SI_InfluenceCard, type.id, type.ident);
}
//Influence Votes
for(uint i = 0, cnt = voteList.length; i < cnt; ++i) {
auto@ type = voteList[i];
file.addIdentifier(SI_InfluenceVote, type.id, type.ident);
}
//Influence Effects
for(uint i = 0, cnt = effectList.length; i < cnt; ++i) {
auto@ type = effectList[i];
file.addIdentifier(SI_InfluenceEffect, type.id, type.ident);
}
//Influence Clauses
for(uint i = 0, cnt = clauseList.length; i < cnt; ++i) {
auto@ type = clauseList[i];
file.addIdentifier(SI_InfluenceClause, type.id, type.ident);
}
}
//}}