#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 hooks; array 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 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 RARITY_DEFAULTS = {10, 4, 3, 2, 1}; int totalCardFrequency = 0; array 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(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 hooks; array 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 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 empireVotes(getEmpireCount(), 0); array contribPoints(getEmpireCount(), 0); array offers; uint nextOfferId = 0; int totalFor = 0; int totalAgainst = 0; array events; array 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 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(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 tags; array hooks; void init() { for(uint i = 0, cnt = hooks.length; i < cnt; ++i) { auto@ hook = cast(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 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 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(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 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 clauses; uint visibleMask = uint(~0); uint inviteMask = 0; uint presentMask = 0; Empire@ leader; array 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 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 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(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(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(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(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 offers; array 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); } } //}}