// log_pet_stats.nss
// 2014-04-24 r002 DarthGizka
//
// call the script to start the test, call it again to abort (or move away from the summoner)
//
// bin_ship/ECLog.ini must exist, with "Script = 1" in section [LogTypes]
#include "events_h" // EVENT_TYPE_xxx
#include "global_objects_h" // RESOURCE_SCRIPT_CREATURE_CORE
void handle_event_ (event e);
void start_test_series_ ();
int unsummon_summoner_ ();
void main ()
{
event e = GetCurrentEvent();
if (IsEventValid(e))
{
handle_event_(e);
return;
}
// remove summoner if present, otherwise start a new test series
if (!unsummon_summoner_())
{
start_test_series_();
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
const int MIN_LEVEL_ = 1; // clamping levels can be useful during development because a full
const int MAX_LEVEL_ = 99; // test series takes quite a while otherwise
const string LOG_PREFIX_ = "log_pet_stats_nss__20140424";
const string SUMMONER_TAG_ = "log_pet_stats_nss__summoner";
const float ABORT_DISTANCE_ = 5.0f; // moving the PC away from the summoner stops the script
const int SAFE_STAMINA_RESERVE_ = 250; // maximum needed is 120 for the spider
const int CYCLE_STEP_NONE_ = 0;
const int CYCLE_STEP_WOLF_ = 1;
const int CYCLE_STEP_BEAR_ = 2;
const int CYCLE_STEP_SPIDER_ = 3;
const int CYCLE_STEP_LEVEL_ = 4;
const int CYCLE_STEP_DONE_ = 5;
// 'imports' from tpv_utility_h
void floaty_ (string s, int colour = 0xFFFFFF, object above_whom = OBJECT_INVALID, float duration = 3.0f);
vector horizontal_vector_ (float angle, float magnitude);
string trim_ (string s);
///////////////////////////////////////////////////////////////////////////////////////////////////
int is_pet_summoning_command_ (event command_complete_event);
int find_and_log_current_pet_ (object summoner);
int initiate_next_cycle_step_ (object summoner, int next_step);
int level_summoner_ (object summoner, int level = 0);
void handle_event_ (event e)
{
switch (GetEventType(e))
{
case EVENT_TYPE_SPAWN:
{
int original_level = GetLevel(OBJECT_SELF);
HandleEvent(e, RESOURCE_SCRIPT_CREATURE_CORE);
if (GetLevel(OBJECT_SELF) != original_level)
{
// creature_core smashed the character level
level_summoner_(OBJECT_SELF, original_level);
}
return;
}
case EVENT_TYPE_CUSTOM_COMMAND_COMPLETE:
{
if (GetDistanceBetween(OBJECT_SELF, GetHero()) >= ABORT_DISTANCE_)
{
floaty_("*ABORTED*", 0xFF0000);
return;
}
/** /
floaty_("0:" + IntToString(GetEventInteger(e, 0))
+ " 1:" + IntToString(GetEventInteger(e, 1))
+ " 2:" + IntToString(GetEventInteger(e, 2))
+ " 3:" + IntToString(GetEventInteger(e, 3)) );
/**/
if (is_pet_summoning_command_(e))
{
int current_step = find_and_log_current_pet_(OBJECT_SELF);
if (current_step > CYCLE_STEP_NONE_)
{
initiate_next_cycle_step_(OBJECT_SELF, current_step + 1);
}
return;
}
}
}
HandleEvent(e, RESOURCE_SCRIPT_CREATURE_CORE);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// *todo* add stats needed for damage calculation (e.g. STR)
int[] properties_to_log_ ()
{
int i = 0;
int[] p;
p[i++] = PROPERTY_SIMPLE_LEVEL;
p[i++] = PROPERTY_DEPLETABLE_HEALTH;
p[i++] = PROPERTY_ATTRIBUTE_REGENERATION_HEALTH_COMBAT;
p[i++] = PROPERTY_DEPLETABLE_MANA_STAMINA;
p[i++] = PROPERTY_ATTRIBUTE_REGENERATION_STAMINA_COMBAT;
p[i++] = PROPERTY_ATTRIBUTE_ATTACK;
p[i++] = PROPERTY_ATTRIBUTE_AP;
p[i++] = PROPERTY_ATTRIBUTE_DAMAGE_BONUS;
p[i++] = PROPERTY_ATTRIBUTE_DEFENSE;
p[i++] = PROPERTY_ATTRIBUTE_ARMOR;
p[i++] = PROPERTY_ATTRIBUTE_DISPLACEMENT;
p[i++] = PROPERTY_ATTRIBUTE_RESISTANCE_MENTAL;
p[i++] = PROPERTY_ATTRIBUTE_RESISTANCE_PHYSICAL;
p[i++] = 52; // PROPERTY_ATTRIBUTE_SPELLRESISTANCE_
p[i++] = PROPERTY_ATTRIBUTE_DAMAGE_RESISTANCE_FIRE;
p[i++] = PROPERTY_ATTRIBUTE_DAMAGE_RESISTANCE_COLD;
p[i++] = PROPERTY_ATTRIBUTE_DAMAGE_RESISTANCE_ELEC;
p[i++] = PROPERTY_ATTRIBUTE_DAMAGE_RESISTANCE_SPIRIT;
p[i++] = PROPERTY_ATTRIBUTE_DAMAGE_RESISTANCE_NATURE;
return p;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// http://social.bioware.com/wiki/datoolset/index.php/EVENT_TYPE_COMMAND_COMPLETE
int is_pet_summoning_ability_ (int ability_id);
int is_pet_summoning_command_ (event command_complete_event)
{
if (GetEventInteger(command_complete_event, 0) == COMMAND_TYPE_USE_ABILITY)
{
if (GetEventInteger(command_complete_event, 1) != 1)
{
floaty_("evt[1] == " + IntToString(GetEventInteger(command_complete_event, 1)), 0xFF0000);
return FALSE;
}
return is_pet_summoning_ability_(GetEventInteger(command_complete_event, 2));
}
return FALSE;
}
//-------------------------------------------------------------------------------------------------
int unsummon_pets_ (object summoner)
{
object[] party = GetPartyList(summoner);
int i, n = GetArraySize(party), num_destroyed = 0;
for (i = 0; i < n; ++i)
{
if (IsSummoned(party[i]))
{
DestroyObject(party[i]);
++num_destroyed;
}
}
return num_destroyed;
}
//-------------------------------------------------------------------------------------------------
int unsummon_summoner_ ()
{
object[] o = GetObjectsInArea(GetArea(GetMainControlled()), SUMMONER_TAG_);
int i, n = GetArraySize(o);
for (i = 0; i < n; ++i)
{
unsummon_pets_(o[i]);
DestroyObject(o[i]);
}
return n;
}
//-------------------------------------------------------------------------------------------------
int is_master_ranger_ (object summoner)
{
return HasAbility(summoner, ABILITY_TALENT_MASTER_RANGER);
}
//-------------------------------------------------------------------------------------------------
int level_summoner_ (object summoner, int level = 0)
{
if (level <= 0)
{
level = Max(0, GetLevel(summoner)) + 1;
}
level = Max(MIN_LEVEL_, level);
if (level > Min(MAX_LEVEL_, GetMaxLevel()))
{
return FALSE;
}
SetCreatureProperty(summoner, PROPERTY_SIMPLE_LEVEL, IntToFloat(level), PROPERTY_VALUE_BASE);
if (GetLevel(summoner) != level)
{
floaty_("level_summoner_(" + IntToString(level) + ") -> " + IntToString(GetLevel(summoner)), 0xFF0000);
return FALSE;
}
floaty_((is_master_ranger_(summoner) ? "Master " : "") + "Ranger " + IntToString(level));
return TRUE;
}
//-------------------------------------------------------------------------------------------------
int make_master_ (object summoner, int level = 0)
{
if (is_master_ranger_(summoner))
{
return FALSE;
}
AddAbility(summoner, ABILITY_TALENT_MASTER_RANGER);
return level <= 0 || level_summoner_(summoner, level);
}
//-------------------------------------------------------------------------------------------------
// no setting of tags etc., in case we want to prep the real Leliana
void prep_summoner_ (object summoner, int level = 1)
{
AddAbility(summoner, ABILITY_TALENT_HIDDEN_RANGER);
AddAbility(summoner, ABILITY_TALENT_NATURE_I_COURAGE_OF_THE_PACK);
AddAbility(summoner, ABILITY_TALENT_NATURE_II_HARDINESS_OF_THE_BEAR);
AddAbility(summoner, ABILITY_TALENT_SUMMON_SPIDER);
SetEventScript(summoner, GetCurrentScriptResource());
// strange syntax for EnablevEvent(summoner, TRUE, EVENT_TYPE_CUSTOM_COMMAND_COMPLETE):
SetLocalInt(summoner, "AI_CUSTOM_AI_ACTIVE", 1);
level_summoner_(summoner, level);
}
//-------------------------------------------------------------------------------------------------
int ability_for_step_ (int step)
{
switch (step)
{
case CYCLE_STEP_WOLF_ : return ABILITY_TALENT_NATURE_I_COURAGE_OF_THE_PACK;
case CYCLE_STEP_BEAR_ : return ABILITY_TALENT_NATURE_II_HARDINESS_OF_THE_BEAR;
case CYCLE_STEP_SPIDER_ : return ABILITY_TALENT_SUMMON_SPIDER;
}
return 0;
}
//-------------------------------------------------------------------------------------------------
int is_pet_summoning_ability_ (int ability_id)
{
switch (ability_id)
{
case ABILITY_TALENT_NATURE_I_COURAGE_OF_THE_PACK:
case ABILITY_TALENT_NATURE_II_HARDINESS_OF_THE_BEAR:
case ABILITY_TALENT_SUMMON_SPIDER:
{
return TRUE;
}
}
return FALSE;
}
//-------------------------------------------------------------------------------------------------
int do_summon_ (object summoner, int step)
{
int ability_id = ability_for_step_(step);
if (!IsModalAbilityActive(summoner, ability_id))
{
SetCreatureStamina(summoner, Max(SAFE_STAMINA_RESERVE_, GetCreatureStamina(summoner)));
SetCooldown(summoner, ability_id, 0.0f);
}
ClearAllCommands(summoner, TRUE);
return AddCommand(summoner, CommandUseAbility(ability_id, summoner), TRUE, TRUE);
}
//-------------------------------------------------------------------------------------------------
void log_ (string text, int flush = FALSE)
{
PrintToLog(LOG_PREFIX_ + " " + text);
if (flush) PrintToLogAndFlush("");
}
//-------------------------------------------------------------------------------------------------
void log_begin_end_ (string tag)
{
string s = tag
+ " " + IntToString(GetGameDifficulty())
+ " " + IntToString(GetTime())
+ " " + GetResRef(GetArea(GetHero()));
log_(s, TRUE);
}
//-------------------------------------------------------------------------------------------------
void summoning_series_finished_ (object summoner)
{
object controlled = GetMainControlled();
if (controlled == summoner)
{
controlled = GetHero();
}
if (controlled != summoner)
{
vector direction = GetPosition(controlled) - GetPosition(summoner);
AddCommand(summoner, CommandTurn(180.0f - VectorToAngle(direction)), TRUE, TRUE);
}
floaty_("*DONE*", 0xFFFF00);
log_begin_end_("<<<");
}
//-------------------------------------------------------------------------------------------------
int initiate_next_cycle_step_ (object summoner, int next_step)
{
switch (next_step)
{
case CYCLE_STEP_WOLF_:
case CYCLE_STEP_BEAR_:
case CYCLE_STEP_SPIDER_:
{
break;
}
case CYCLE_STEP_LEVEL_:
{
if (level_summoner_(summoner) || make_master_(summoner, MIN_LEVEL_))
{
next_step = CYCLE_STEP_WOLF_;
break;
}
}
/*FALL THRU*/
default:
{
if (next_step != CYCLE_STEP_LEVEL_)
{
floaty_("next(" + IntToString(next_step) + ")", 0xFF0000);
}
}
/*FALL THRU*/
case CYCLE_STEP_DONE_:
{
summoning_series_finished_(summoner);
return FALSE;
}
}
return do_summon_(summoner, next_step);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
object create_and_prep_summoner_ ();
void start_test_series_ ()
{
log_begin_end_(">>>");
object summoner = create_and_prep_summoner_();
if (IsObjectValid(summoner))
{
ClearAllCommands(summoner, TRUE);
do_summon_(summoner, CYCLE_STEP_WOLF_);
}
}
//-------------------------------------------------------------------------------------------------
// can't return a resource array from a function?
object create_first_available_summoner_candidate_ (location where)
{
resource[] candidates;
int i = 0;
/** /
candidates[i++] = R"den300cr_shianni.utc"; // doesn't summon
candidates[i++] = R"house_cat.utc"; // doesn't summon
/**/
candidates[i++] = R"succubus.utc";
for (i = 0; i < GetArraySize(candidates); ++i)
{
object summoner = CreateObject(OBJECT_TYPE_CREATURE, candidates[i], where);
if (IsObjectValid(summoner))
{
return summoner;
}
}
return OBJECT_INVALID;
}
//-------------------------------------------------------------------------------------------------
object create_and_prep_summoner_ ()
{
object hero = GetMainControlled();
float angle = 180.0f - GetFacing(hero);
vector shift = horizontal_vector_(angle + 60.0f, 2.0f);
location loc = GetSafeLocation(Location(GetArea(hero), GetPosition(hero) + shift, 240.0f - angle));
object summoner = create_first_available_summoner_candidate_(loc);
if (IsObjectValid(summoner))
{
SetGroupId(summoner, GROUP_NEUTRAL);
SetTag(summoner, SUMMONER_TAG_);
prep_summoner_(summoner, MIN_LEVEL_);
}
return summoner;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
void log_pet_for_summoner_ (object pet, object summoner);
int classify_pet_res_ref_ (string res_ref);
int find_and_log_current_pet_ (object summoner)
{
object[] party = GetPartyList(summoner);
int i, step = CYCLE_STEP_NONE_;
for (i = 0; i < GetArraySize(party); ++i)
{
object critter = party[i];
if (IsSummoned(critter))
{
step = classify_pet_res_ref_(GetResRef(critter));
if (step != CYCLE_STEP_NONE_)
{
log_pet_for_summoner_(critter, summoner);
break;
}
}
}
return step;
}
//-------------------------------------------------------------------------------------------------
// returns the step index corresponding to the pet, STEP_NONE else
int classify_pet_res_ref_ (string res_ref)
{
if (StringRight(res_ref, 4) == ".utc")
{
res_ref = StringLeft(res_ref, GetStringLength(res_ref) - 4);
}
if (res_ref == "wolf" || res_ref == "wolf_blight")
{
return CYCLE_STEP_WOLF_;
}
if (res_ref == "bear_black" || res_ref == "bear_great")
{
return CYCLE_STEP_BEAR_;
}
if (res_ref == "spider_giant" || res_ref == "spider_poisonous")
{
return CYCLE_STEP_SPIDER_;
}
return CYCLE_STEP_NONE_;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
string f2s_ (float value, int decimals = 2)
{
string s = trim_(FloatToString(value, 18, decimals));
if (StringLeft(s, 1) == ".")
{
s = "0" + s;
}
while (StringRight(s, 1) == "0")
{
s = StringLeft(s, GetStringLength(s) - 1);
}
if (StringRight(s, 1) == ".")
{
s = StringLeft(s, GetStringLength(s) - 1);
}
return s == "" ? "0" : s;
}
//-------------------------------------------------------------------------------------------------
string stats_item_ (object critter, int prop_id, int prop_type = PROPERTY_VALUE_TOTAL)
{
return "," + IntToString(prop_id) + ":" + f2s_(GetCreatureProperty(critter, prop_id, prop_type));
}
//-------------------------------------------------------------------------------------------------
string level_text_ (int level)
{
return (level < 10 ? "0" : "") + IntToString(level);
}
//-------------------------------------------------------------------------------------------------
void log_pet_for_summoner_ (object pet, object summoner)
{
int[] properties = properties_to_log_();
int i;
string s = GetResRef(summoner)
+ "," + level_text_(GetLevel(summoner))
+ "," + (is_master_ranger_(summoner) ? "M" : "R")
+ "," + GetResRef(pet);
for (i = 0; i < GetArraySize(properties); ++i)
{
s = s + stats_item_(pet, properties[i]);
}
log_(s);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// tpv_utilty_h
void floaty_ (string s, int colour = 0xFFFFFF, object above_whom = OBJECT_INVALID, float duration = 3.0f)
{
if (above_whom == OBJECT_INVALID)
{
above_whom = GetMainControlled();
}
DisplayFloatyMessage(above_whom, s, FLOATY_MESSAGE, colour, duration);
}
vector vscale_ (vector v, float l)
{
float m = GetVectorMagnitude(v);
if (m > 0.0f)
{
return v * l / m;
}
return v;
}
vector horizontal_vector_ (float angle, float magnitude)
{
return vscale_(AngleToVector(angle), magnitude);
}
string trim_ (string s)
{
int l = GetStringLength(s);
while (StringLeft(s, 1) == " ")
{
s = StringRight(s, --l);
}
while (StringRight(s, 1) == " ")
{
s = StringLeft(s, --l);
}
return s;
}
Note: I wrote the code 'blind', on a computer without the toolset. That is why I had to structure a bit more aggressively than usual, in order to facilitate debugging and reformulating parts of the logic using existing 'words'. As it turned out that wasn't needed, but it should make it easier to reuse bits for other experiments.