Skip to content

Instantly share code, notes, and snippets.

@Suor
Created November 11, 2020 15:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Suor/966c29ee15d9b2b7b04e404aba85b735 to your computer and use it in GitHub Desktop.
Save Suor/966c29ee15d9b2b7b04e404aba85b735 to your computer and use it in GitHub Desktop.
Logger pretty printer in Squirrel
::mods_registerMod("mod_standout_foes", 0.1, "Standout foes");
local gt = this.getroottable();
// Alias to make it easier for us inside. Things are still global and accessible from outside,
// so if anyone will want to write a mod for this mod then it should be easy enough.
local sf = gt.StandoutFoes <- {};
local Debug = gt.StandoutFoes.Debug <- {};
gt.StandoutFoes.Quirks <- {
fast = {
prefix = "Fast",
XPMult = 1.2,
function apply(e) {
// More action points and initiave
e.m.ActionPoints += 2;
e.m.BaseProperties.ActionPoints += 2;
e.m.BaseProperties.Initiative += 25;
// TODO: vary chance on scale/diff?
// Give adrenaline probably
if (sf.Rand.chance(0.5)) {
e.m.Skills.add(this.new("scripts/skills/perks/perk_adrenalin"));
e.m.AIAgent.addBehavior(this.new("scripts/ai/tactical/behaviors/ai_adrenaline"));
}
// TODO: give dodge?
// More action points plus adrenaline drains stamina so we add some
e.m.BaseProperties.Stamina += 25;
e.m.BaseProperties.FatigueRecoveryRate += 5;
// Being fast helps hit and not being hit
sf.Util.tweakOffence(e, 8);
sf.Util.tweakDefence(e, 5);
// Slightly red head
sf.Util.color(e, "head", "#ffdddd", 0.9);
}
},
big = {
prefix = "Big",
XPMult = 1.2,
function apply(e) {
sf.Util.tweakOffence(e, 0, 1.25);
sf.Util.tweakDefence(e, -5, 1.7);
sf.Util.scale(e, 1.12);
}
}
}
sf.partyStats <- function(party) {
this.logInfo("sf: partyStats " + party.getName());
local stats = {
factionType = this.World.FactionManager.getFaction(party.getFaction()).getType(),
size = party.m.Troops.len(),
}
return stats;
}
// Quirk helpers
gt.StandoutFoes.Util <- {
function applyQuirk(e, quirk) {
this.logInfo("Apply " + quirk.prefix + " to " + e.getName());
e.m.Name = quirk.prefix + " " + e.m.Name;
e.m.XP *= quirk.XPMult;
quirk.apply(e);
}
function tweakOffence(e, skill, damageMult = 1.0) {
local b = e.m.BaseProperties;
b.MeleeSkill += skill;
b.RangedSkill += skill;
b.DamageTotalMult *= damageMult;
}
function tweakDefence(e, def, hpMult = 1.0) {
local b = e.m.BaseProperties;
b.MeleeDefense += def;
b.RangedDefense += def;
b.HitpointsMult *= hpMult;
}
// Presentation utils
function scale(e, scaleMult) {
// This doesn't take care of the corpse size unfortunately
local parts = [
"body", "surcoat", "tattoo_body", "armor",
"head", "hair", "beard", "beard_top", "tattoo_head", "helmet"
];
// "accessory", "accessory_special" ???
foreach (part in parts) {
e.getSprite(part).Scale *= scaleMult;
}
}
function color(e, part, color, brightness = 1, saturation = 1) {
local sprite = e.getSprite(part);
sprite.Color = this.createColor(color);
sprite.setBrightness(brightness);
sprite.Saturation = saturation;
}
}
// Utilities
gt.StandoutFoes.Rand <- {
chance = @(prob) this.Math.rand(1, 1000) / 1000 <= prob,
choice = @(arr) arr[this.Math.rand(0, arr.len() - 1)]
}
// Debug things
local PP_MAX_LENGTH = 50;
local function indent(level, s) {
return format("%"+ (level * 4) + "s", "") + s;
}
local function join(sep, lines) {
local s = "";
foreach (i, line in lines) {
if (i > 0) s += sep;
s += line;
}
return s;
}
local function joinLength(items, sepLen) {
if (items.len() == 0) return 0;
return items.map(@(s) s.len()).reduce(@(a, b) a + b) + (items.len() - 1) * sepLen;
}
local function mapTable(data, func) {
local res = [];
// this.logInfo
foreach (key, value in data) res.push(func(key, value));
return res;
}
Debug.pp <- function(data, level = 0) {
local function ppCont(items, level, start, end) {
if (joinLength(items, 2) <= PP_MAX_LENGTH - level * 4 - 2) {
return start + join(", ", items) + end + (level == 0 ? "\n" : "");
} else {
local lines = [start];
lines.extend(items.map(@(item) indent(level + 1, item)));
lines.push(indent(level, end));
return join("\n", lines) + "\n";
}
}
if (typeof data == "table") {
local items = mapTable(data, @(k, v) k + " = " + Debug.pp(v, level + 1));
return ppCont(items, level, "{", "}");
} else if (typeof data == "array") {
local items = data.map(@(item) Debug.pp(item, level + 1));
return ppCont(items, level, "[", "]");
} else {
return "" + data; // More robust than .tostring()
}
}
Debug.log <- function(name, data) {
this.logInfo("<pre>sf: " + name + " = " + Debug.pp(data) + "</pre>");
}
::mods_queue("mod_standout_foes", null, function()
{
this.logInfo("sf: loading");
::mods_hookClass("entity/tactical/tactical_entity_manager", function(cls) {
this.logInfo("sf: hook tactical_entity_manager");
local setupEntity = cls.setupEntity;
cls.setupEntity = function(e, t) {
setupEntity(e, t);
// this.logInfo("sf: setupEntity " + e.getName() + " troop " + t + " party " + t.Party.getName());
this.logInfo("sf: setupEntity " + e.getName() + " party " + t.Party.getName());
if (!("sf" in t.Party)) t.Party.m.StandoutFoes <- sf.partyStats(t.Party);
Debug.log("troop", t);
Debug.log("party.sf", t.Party.m.StandoutFoes);
Debug.log("party.m", t.Party.m);
// this.logInfo("sf: " + Debug.html("party.sf", t.Party.m.StandoutFoes));
// this.logInfo("sf: troop: " + Debug.html(t));
// this.logInfo("sf: party.m: " + Debug.pp(t.Party.m));
// this.logInfo("sf: end party.m");
// == this.Const.FactionType.Goblins
// foreach (key, value in t) {
// this.logInfo("sf: troop: " + key + " = " + value);
// }
// this.logInfo("sf: party.m typeof: " + typeof t.Party.m);
// foreach (key, value in t.Party.m) {
// this.logInfo("sf: party: " + key);
// this.logInfo("sf: party: " + Debug.html(value));
// }
// foreach (key, value in t.Party.world_entity.m) {
// this.logInfo("sf: party.we: " + key + " = " + value);
// }
// this.logInfo("sf: party.getFaction(): " + t.Party.getFaction());
// foreach (key, value in t.Party.m.Loot) {
// this.logInfo("sf: party.loot: " + key + " = " + value);
// }
// foreach (key, value in t.Party.world_entity.m.Troops[0]) {
// this.logInfo("sf: party.we.troops[0]: " + key + " = " + value);
// }
// foreach (key, value in t.Party.m.Flags) {
// this.logInfo("sf: party.flags: " + key + " = " + value);
// }
local quirks = [sf.Quirks.fast, sf.Quirks.big];
sf.Util.applyQuirk(e, sf.Rand.choice(quirks));
// e.m.Skills.add(this.new("scripts/skills/racial/champion_racial"));
}
});
// local patchPlayer = function(obj) {
// this.logInfo("MVAOP: patching a player object " + obj.getName());
// // Prevent from patching twice.
// // Not sure how this happens, but it does sometimes for new hires, who get levels later.
// if("addExtraPerks" in obj) return;
// // Give rolls of 2 on veteran levels to attrs with talents sometimes
// local getAttributeLevelUpValues = obj.getAttributeLevelUpValues;
// obj.getAttributeLevelUpValues = function()
// {
// if(m.Attributes[0].len() != 0) return getAttributeLevelUpValues(); // no bonus if not in veteran levels
// local v = getAttributeLevelUpValues();
// local extra = function(t, bonus = 0)
// {
// if(t == 0) return 0;
// local max = (t == 1 ? 5 : t == 2 ? 3 : 2) - bonus;
// return max <= 1 || Math.rand(1, max) == 1 ? 1 : 0;
// }
// v.hitpointsIncrease += extra(m.Talents[Const.Attributes.Hitpoints]);
// v.braveryIncrease += extra(m.Talents[Const.Attributes.Bravery]);
// v.fatigueIncrease += extra(m.Talents[Const.Attributes.Fatigue]);
// v.initiativeIncrease += extra(m.Talents[Const.Attributes.Initiative], 1);
// v.meleeSkillIncrease += extra(m.Talents[Const.Attributes.MeleeSkill]);
// v.rangeSkillIncrease += extra(m.Talents[Const.Attributes.RangedSkill]);
// v.meleeDefenseIncrease += extra(m.Talents[Const.Attributes.MeleeDefense]);
// v.rangeDefenseIncrease += extra(m.Talents[Const.Attributes.RangedDefense]);
// return v;
// }
// // Add an extra perk point for each odd level
// local updateLevel = obj.updateLevel;
// obj.updateLevel = function() {
// // this.logInfo("MVAOP: update level for " + this.getName());
// local level = m.Level;
// updateLevel();
// this.addExtraPerks(level);
// }
// // Perks are added manually there, breaking the abstraction
// local onHired = obj.onHired;
// obj.onHired = function() {
// this.logInfo("MVAOP: hired " + this.getName());
// onHired();
// this.addExtraPerks(1);
// }
// obj.addExtraPerks <- function(level) {
// if (level >= m.Level) return;
// this.logInfo("MVAOP: Leveling up " + this.getName() + " from " + level + " to " + m.Level);
// // give a perk point every for odd level
// for (; ++level <= m.Level;)
// if (level % 2 == 1) m.PerkPoints++;
// }
// }
// // Freshly hired bros don't fire new hook and hence don't get a patched class,
// // so we need to patch it like this
// ::mods_hookNewObject("entity/tactical/player", patchPlayer);
// ::mods_hookNewObject("ui/screens/world/modules/world_town_screen/town_hire_dialog_module",
// function (cls) {
// this.logInfo("MVAOP: patching town_hire_dialog_module class");
// local onHireRosterEntry = cls.onHireRosterEntry;
// cls.onHireRosterEntry = function(_entityID) {
// local entry = this.findEntityWithinRoster(_entityID);
// if(entry) patchPlayer(entry);
// return onHireRosterEntry(_entityID)
// }
// }
// );
// // Need to hook this too since perks are added there too,
// // breaking updateLevel() abstraction
// ::mods_hookBaseClass("scenarios/world/starting_scenario", function(cls) {
// this.logInfo("MVAOP: hook starting_scenario");
// local onSpawnAssets = "onSpawnAssets" in cls ? cls.onSpawnAssets : null;
// cls.onSpawnAssets <- function()
// {
// if (onSpawnAssets) onSpawnAssets();
// local roster = World.getPlayerRoster().getAll();
// foreach (bro in roster) bro.addExtraPerks(1);
// }
// });
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment