PDA

View Full Version : Bard Out-Of-Combat/In-Combat Songs


Uleat
08-29-2013, 12:04 PM
bad_captain,

I went ahead and experimented with activating the bard SpellType_InCombatBuff sub-system.

Although my testing used random (and possibly too many) non-combat 'buff' songs, it does allow a secondary song group to be
used in conjunction with the primary combat 'buff' songs.

I only used spells that are available as scribable 'Songs,' but the final list is your choosing.

I think that I covered all of the bases with this, but you should review it to see if I missed anything. The changes should
be self-explanatory.

The default settings are set to pre-modification behavior, so the rules will need to be changed to use the new songs.

---

My observations..

It performs as intended with the selection of songs that I chose.

I don't recommend using Selo's Traveling song due to the severity of levitation and invisibility drop-outs. This will lead to
trouble for players trying to use these features. (Level 49 Selo's is plenty fast in higher levels with proper equipment.)

The regular Selo's Speed songs are not interrupted too badly..definitely better than just dropping them all into type 8.
(The way that I configured the second and third Selo's allows the outdoor to cast first, then check for indoor use.)

Single group combat is usually over fast enough with a decently geared bot group that Selo's doesn't drop off, and it is not
recast during combat..which is good.

There does seem to be a quirk (at least on my system) with the player dropping out of Selo's speed at the 5-seconds remaining
point. Aside from this issue, the sub-system seems ok. (I'll look around, but I don't think it's related to this code.)

I tested all values of the new rules and they work correctly.

---

Now to you..

Is this something you wouldn't mind seeing implemented for bard bots?

(If so..) I just need a list, or suggestions, of songs to use for out of combat.

Any pointers on the AICastSpell (case: SpellType_InCombatBuff) code would be helpful too...

(I went ahead and committed this branch so you can make changes directly and merge afterwards.)

---

As far as the bug fix and cast while moving mod...

In the two noted cases of 'if(0 > 0)' in Bot::AIDoCastSpell, I went ahead a hard-coded a (3 * 6000) timer offset value since
all of the other spells in that line have a buffduration of '3.'
(If this is an issue, we'll have to figure something else out...)

The cast while moving code was added as an 'else if' conditional below where the non-combat Bot::AI_IdleCastCheck is called
in Bot::AI_Process.

---

Patch (branch: 'bardbot_incombatbuff'):


Subject: [PATCH] Modified Bard Bot behavior - Allows in-combat and
out-of-combat songs

---
changelog.txt | 11 ++
common/database.cpp | 30 +++++
common/database.h | 1 +
common/ruletypes.h | 2 +
common/spdat.cpp | 13 +++
common/spdat.h | 1 +
.../2013_08_29_Bot_Bard_Song_Additions.sql | 23 ++++
...13_08_29_Bot_Bard_InCombatBuff_Modification.sql | 10 ++
zone/bot.cpp | 5 +
zone/botspellsai.cpp | 123 ++++++++++++++++++++-
zone/zone.cpp | 1 +
zone/zone.h | 3 +
12 files changed, 218 insertions(+), 5 deletions(-)
create mode 100644 utils/sql/git/optional/2013_08_29_Bot_Bard_Song_Additions.sql
create mode 100644 utils/sql/git/required/2013_08_29_Bot_Bard_InCombatBuff_Modification.sql

diff --git a/changelog.txt b/changelog.txt
index 68060da..4af823c 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,5 +1,16 @@
EQEMu Changelog (Started on Sept 24, 2003 15:50)
-------------------------------------------------------
+== 08/29/2013 ==
+Uleat: Fixed an obscure bug where Bard Bot's were only casting 'Clarity' between levels 20 and 33 when Idle.
+Uleat: Changed Bard Bot Idle/IsMoving behavior to allow casting while not engaged and moving.
+Uleat: Converted current Bard Bot SpellType_Buffs to SpellType_InCombatBuffs to allow usage of a secondary song set. Rules set to original behavior:
+- 'Bots:BotBardUseInCombatSongsOutOfCombat' = 'true'
+- 'Bots:BotBardUseOutOfCombatSongs' = 'false'
+Uleat: Added 'zonetype' property/method to Zone/Database classes. Zone types (ztype) needs to be reviewed because it doesn't appear to match spdat.zonetype enums.
+Uleat: Added 'IsSpellUsableThisZoneType' method to spdat. Framework-only..should not be used until properly coded and zonetypes are qualified.
+required: 2013_08_29_Bot_Bard_InCombatBuff_Modification.sql
+optional: 2013_08_29_Bot_Bard_Song_Additions.sql
+
== 08/20/2013 ==
Uleat: Fix for bot pet spell buff corruption (existing db issues will correct themselves through pet death attrition)

diff --git a/common/database.cpp b/common/database.cpp
index c9dfedb..b6d26b0 100644
--- a/common/database.cpp
+++ b/common/database.cpp
@@ -1352,6 +1352,36 @@ const char* Database::GetZoneName(uint32 zoneID, bool ErrorUnknown) {
return 0;
}

+// returns <database>.zone.ztype - not <database>.zone.type
+uint8 Database::GetZoneType(uint32 zoneID, uint32 version)
+{
+ char errbuf[MYSQL_ERRMSG_SIZE];
+ char *query = 0;
+ MYSQL_RES *result;
+ MYSQL_ROW row;
+ int ztype = 0; // if there's a more appropriate default return value, please change it
+
+ if(RunQuery(query, MakeAnyLenString(&query, "SELECT ztype FROM zone WHERE zoneidnumber='%i' AND (version=%i OR version=0) ORDER BY version DESC", zoneID, version), errbuf, &result))
+ {
+ if(mysql_num_rows(result) > 0)
+ {
+ row = mysql_fetch_row(result);
+ ztype = atoi(row[0]);
+ }
+
+ safe_delete_array(query);
+ mysql_free_result(result);
+ return ztype;
+ }
+ else
+ {
+ std::cerr << "Error in GetZoneType query '" << query << "' " << errbuf << std::endl;
+ }
+
+ safe_delete_array(query);
+ return ztype;
+}
+
uint8 Database::GetPEQZone(uint32 zoneID, uint32 version){
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
diff --git a/common/database.h b/common/database.h
index 849f6f6..197c4ef 100644
--- a/common/database.h
+++ b/common/database.h
@@ -234,6 +234,7 @@ public:
uint32 GetZoneID(const char* zonename);
uint8 GetPEQZone(uint32 zoneID, uint32 version);
const char* GetZoneName(uint32 zoneID, bool ErrorUnknown = false);
+ uint8 GetZoneType(uint32 zoneID, uint32 version);
uint8 GetServerType();
bool GetSafePoints(const char* short_name, uint32 version, float* safe_x = 0, float* safe_y = 0, float* safe_z = 0, int16* minstatus = 0, uint8* minlevel = 0, char *flag_needed = nullptr);
bool GetSafePoints(uint32 zoneID, uint32 version, float* safe_x = 0, float* safe_y = 0, float* safe_z = 0, int16* minstatus = 0, uint8* minlevel = 0, char *flag_needed = nullptr) { return GetSafePoints(GetZoneName(zoneID), version, safe_x, safe_y, safe_z, minstatus, minlevel, flag_needed); }
diff --git a/common/ruletypes.h b/common/ruletypes.h
index bdba9f3..4828cb3 100644
--- a/common/ruletypes.h
+++ b/common/ruletypes.h
@@ -422,6 +422,8 @@ RULE_BOOL ( Bots, BotGroupBuffing, false ) // Bots will cast single target buffs
RULE_BOOL ( Bots, BotSpellQuest, false ) // Anita Thrall's (Anita_Thrall.pl) Bot Spell Scriber quests.
RULE_INT ( Bots, BotAAExpansion, 8 ) // Bots get AAs through this expansion
RULE_BOOL ( Bots, BotGroupXP, false ) // Determines whether client gets xp for bots outside their group.
+RULE_BOOL ( Bots, BotBardUseInCombatSongsOutOfCombat, true) // Allows in-combat buff songs to be used out of combat. (Original behavior)
+RULE_BOOL ( Bots, BotBardUseOutOfCombatSongs, false) // Determines whether bard bots use additional out of combat songs.
RULE_CATEGORY_END()
#endif

diff --git a/common/spdat.cpp b/common/spdat.cpp
index 4cf4ab1..75b74cf 100644
--- a/common/spdat.cpp
+++ b/common/spdat.cpp
@@ -1102,6 +1102,19 @@ bool IsShortDurationBuff(uint16 spell_id)
return false;
}

+// Before this can be used, the ztype value for zones will need to be verified..
+// ..and this code will need to have the proper checks put into place -U
+bool IsSpellUsableThisZoneType(uint16 spell_id, uint8 zone_type)
+{
+ if((spell_id > 0) && (spell_id < SPDAT_RECORDS))
+ {
+ // This specifically for Bard Bot "Selo's Rhythm of Speed" song checks
+ if(spells[spell_id].zonetype == -1) { return true; }
+ }
+
+ return false;
+}
+
const char* GetSpellName(int16 spell_id)
{
return( spells[spell_id].name );
diff --git a/common/spdat.h b/common/spdat.h
index 5d317f6..86f1c60 100644
--- a/common/spdat.h
+++ b/common/spdat.h
@@ -806,6 +806,7 @@ bool DetrimentalSpellAllowsRest(uint16 spell_id);
uint32 GetNimbusEffect(uint16 spell_id);
int32 GetFuriousBash(uint16 spell_id);
bool IsShortDurationBuff(uint16 spell_id);
+bool IsSpellUsableThisZoneType(uint16 spell_id, uint8 ztype);
const char *GetSpellName(int16 spell_id);

#endif
diff --git a/utils/sql/git/optional/2013_08_29_Bot_Bard_Song_Additions.sql b/utils/sql/git/optional/2013_08_29_Bot_Bard_Song_Additions.sql
new file mode 100644
index 0000000..f3e9251
--- /dev/null
+++ b/utils/sql/git/optional/2013_08_29_Bot_Bard_Song_Additions.sql
@@ -0,0 +1,23 @@
+-- Bard Bot Out-Of-Combat Buff song additions
+-- (Selo's Traveling Song not included due to the invis/lev issues)
+-- (Song 4395 is usable indoors, so it retains priority 2 against 2605)
+
+
+-- Add new Out-Of-Combat Bard Bot songs
+INSERT INTO `npc_spells_entries` (`npc_spells_id`, `spellid`, `type`, `minlevel`, `maxlevel`, `priority`) VALUES
+(711, 717, 8, 5, 24, 1),
+(711, 4395, 8, 25, 255, 2),
+(711, 2605, 8, 49, 255, 1),
+(711, 735, 8, 24, 255, 3),
+(711, 2602, 8, 15, 255, 3),
+(711, 1765, 8, 59, 255, 3),
+(711, 2603, 8, 30, 255, 3);
+
+
+-- 717 - "Selo's Accelerando"
+-- 4395 - "Selo's Rhythm of Speed" (indoor usable)
+-- 2605 - "Selo's Accelerating Chorus"
+-- 735 - "Lyssa's Veracious Concord"
+-- 2602 - "Song of Sustenance"
+-- 1765 - "Solon's Charismatic Concord"
+-- 2603 - "Amplification"
diff --git a/utils/sql/git/required/2013_08_29_Bot_Bard_InCombatBuff_Modification.sql b/utils/sql/git/required/2013_08_29_Bot_Bard_InCombatBuff_Modification.sql
new file mode 100644
index 0000000..6931499
--- /dev/null
+++ b/utils/sql/git/required/2013_08_29_Bot_Bard_InCombatBuff_Modification.sql
@@ -0,0 +1,10 @@
+-- Bard Bot In-Combat Buff song modifications
+
+
+-- Rule Values update (Default keeps original behavior)
+INSERT INTO `rule_values` VALUES (1, 'Bots:BotBardUseInCombatSongsOutOfCombat', 'true', 'Allow bard bot to use in-combat buff songs while out-of-combat');
+INSERT INTO `rule_values` VALUES (1, 'Bots:BotBardUseOutOfCombatSongs', 'false', 'Grants bard bot access to additional non-combat songs');
+
+
+-- Move current Bard Bot songs from SpellType_Buffs to SpellType_InCombatBuffs
+UPDATE `npc_spells_entries` SET `type` = 1024 WHERE `npc_spells_id` = 711 and `type` = 8;
diff --git a/zone/bot.cpp b/zone/bot.cpp
index c5e2900..a546bd0 100644
--- a/zone/bot.cpp
+++ b/zone/bot.cpp
@@ -3920,6 +3920,11 @@ void Bot::AI_Process() {
}

}
+ // This allows bards to cast songs while moving (and minimizes disruptions to Selo's Speed songs)
+ else if((GetClass() == BARD) && IsMoving() && AIthink_timer->Check() && !spellend_timer.Enabled())
+ {
+ if(GetBotStance() != BotStancePassive) { AI_IdleCastCheck(); }
+ }

if(AImovement_timer->Check()) {
if(GetFollowID()) {
diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp
index 191ed3a..d3d811c 100644
--- a/zone/botspellsai.cpp
+++ b/zone/botspellsai.cpp
@@ -333,7 +333,10 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) {
// Put the zone levitate and movement check here since bots are able to bypass the client casting check
if((IsEffectInSpell(selectedBotSpell.SpellId, SE_Levitate) && !zone->CanLevitate())
|| (IsEffectInSpell(selectedBotSpell.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor())) {
- continue;
+ if(botClass != BARD || !IsSpellUsableThisZoneType(selectedBotSpell.SpellI d, zone->GetZoneType()))
+ {
+ continue;
+ }
}

switch(tar->GetArchetype())
@@ -583,6 +586,84 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) {
break;
}
}
+ else if(botClass == BARD) { // TODO: Adapt to Bard InCombatBuffs songs
+ if (tar->DontBuffMeBefore() < Timer::GetCurrentTime()) {
+ std::list<BotSpell> incombatbuffSpellList = GetBotSpellsBySpellType(this, SpellType_InCombatBuff);
+
+ for(std::list<BotSpell>::iterator itr = incombatbuffSpellList.begin(); itr != incombatbuffSpellList.end(); itr++) {
+ BotSpell selectedBotSpell = *itr;
+
+ if(selectedBotSpell.SpellId == 0)
+ continue;
+
+ // no buffs with illusions.. use #bot command to cast illusions
+ if(IsEffectInSpell(selectedBotSpell.SpellId, SE_Illusion) && tar != this)
+ continue;
+
+ //no teleport spells use #bot command to cast teleports
+ if(IsEffectInSpell(selectedBotSpell.SpellId, SE_Teleport) || IsEffectInSpell(selectedBotSpell.SpellId, SE_Succor))
+ continue;
+
+ // can not cast buffs for your own pet only on another pet that isn't yours
+ if((spells[selectedBotSpell.SpellId].targettype == ST_Pet) && (tar != this->GetPet()))
+ continue;
+
+ // Validate target
+
+ if(!((spells[selectedBotSpell.SpellId].targettype == ST_Target || spells[selectedBotSpell.SpellId].targettype == ST_Pet || tar == this ||
+ spells[selectedBotSpell.SpellId].targettype == ST_Group || spells[selectedBotSpell.SpellId].targettype == ST_GroupTeleport ||
+ (botClass == BARD && spells[selectedBotSpell.SpellId].targettype == ST_AEBard))
+ && !tar->IsImmuneToSpell(selectedBotSpell.SpellId, this)
+ && (tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0))) {
+ continue;
+ }
+
+ // Put the zone levitate and movement check here since bots are able to bypass the client casting check
+ if((IsEffectInSpell(selectedBotSpell.SpellId, SE_Levitate) && !zone->CanLevitate())
+ || (IsEffectInSpell(selectedBotSpell.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor())) {
+ continue;
+ }
+
+ switch(tar->GetArchetype())
+ {
+ case ARCHETYPE_CASTER:
+ //TODO: probably more caster specific spell effects in here
+ if(IsEffectInSpell(selectedBotSpell.SpellId, SE_AttackSpeed) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ATK) ||
+ IsEffectInSpell(selectedBotSpell.SpellId, SE_STR) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ReverseDS))
+ {
+ continue;
+ }
+ break;
+ case ARCHETYPE_MELEE:
+ if(IsEffectInSpell(selectedBotSpell.SpellId, SE_IncreaseSpellHaste) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ManaPool) ||
+ IsEffectInSpell(selectedBotSpell.SpellId, SE_CastingLevel) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ManaRegen_v2) ||
+ IsEffectInSpell(selectedBotSpell.SpellId, SE_CurrentMana))
+ {
+ continue;
+ }
+ break;
+ case ARCHETYPE_HYBRID:
+ //Hybrids get all buffs
+ default:
+ break;
+ }
+
+ if(CheckSpellRecastTimers(this, itr->SpellIndex))
+ {
+
+ uint32 TempDontBuffMeBefore = tar->DontBuffMeBefore();
+
+ castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontBuffMeBefore);
+
+ if(TempDontBuffMeBefore != tar->DontBuffMeBefore())
+ tar->SetDontBuffMeBefore(TempDontBuffMeBefore);
+ }
+
+ if(castedSpell)
+ break;
+ }
+ }
+ }
break;
}
case SpellType_Lifetap: {
@@ -841,6 +922,16 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
if(GetClass() == BARD && IsGroupSpell(AIspells[i].spellid)) {
AIspells[i].time_cancast = (spells[AIspells[i].spellid].recast_time > (spells[AIspells[i].spellid].buffduration * 6000)) ? Timer::GetCurrentTime() + spells[AIspells[i].spellid].recast_time : Timer::GetCurrentTime() + spells[AIspells[i].spellid].buffduration * 6000;
//spellend_timer.Start(spells[AIspells[i].spellid].cast_time);
+
+ // Bard Clarity songs (spellids: 723 & 1287) are recast_time = 0 and buffduration = 0.
+ // This translates to an insta-recast in the above check and is essentially locking all idle bard songs
+ // between levels 20 & 33 to one of the clarity songs.
+
+ // Hard-coded fix of '0/0' bard songs..watch for issues in other song lines
+ if(spells[AIspells[i].spellid].recast_time == 0 && spells[AIspells[i].spellid].buffduration == 0)
+ {
+ AIspells[i].time_cancast += (3 * 6000); // pseudo 'buffduration' of 3 (value matches others in bard 'heal' song line)
+ }
}
else
AIspells[i].time_cancast = Timer::GetCurrentTime() + spells[AIspells[i].spellid].recast_time;
@@ -936,8 +1027,11 @@ bool Bot::AI_IdleCastCheck() {
// bard bots
if(!AICastSpell(this, 100, SpellType_Cure)) {
if(!AICastSpell(this, 100, SpellType_Heal)) {
- if(!AICastSpell(this, 100, SpellType_Buff)) {
- //
+ if(!RuleB(Bots, BotBardUseOutOfCombatSongs) || !AICastSpell(this, 100, SpellType_Buff)) { // skips if rule is false
+ // this tries to keep some combat buffs on the group until engaged code can pick up the buffing
+ if(!RuleB(Bots, BotBardUseInCombatSongsOutOfCombat) || !AICastSpell(this, 100, SpellType_InCombatBuff)) { // skips if rule is false
+ //
+ }
}
}
}
@@ -1140,7 +1234,7 @@ bool Bot::AI_EngagedCastCheck() {
}
}
else if(botClass == BARD) {
- if(!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Buff), SpellType_Buff)) {
+ if(!AICastSpell(this, GetChanceToCastBySpellType(SpellType_InCombatBuff) , SpellType_InCombatBuff)) {
if(!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) {
if(!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Dispel), SpellType_Dispel)) {// Bards will use their debuff songs
if(!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType _Nuke), SpellType_Nuke)) {// Bards will use their debuff songs
@@ -2973,11 +3067,30 @@ void Bot::CalcChanceToCast() {
break;
}
break;
+ case BARD:
+ switch(botStance)
+ {
+ case BotStanceBalanced:
+ case BotStanceReactive:
+ castChance = 100;
+ break;
+ case BotStanceEfficient:
+ case BotStanceAggressive:
+ castChance = 50;
+ break;
+ case BotStanceBurn:
+ case BotStanceBurnAE:
+ castChance = 25;
+ break;
+ default:
+ castChance = 0;
+ break;
+ }
+ break;
case BEASTLORD:
case MAGICIAN:
case DRUID:
case ENCHANTER:
- case BARD:
case WIZARD:
case NECROMANCER:
case SHADOWKNIGHT:
diff --git a/zone/zone.cpp b/zone/zone.cpp
index 0ef69d3..6b61a41 100644
--- a/zone/zone.cpp
+++ b/zone/zone.cpp
@@ -850,6 +850,7 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name)
zoneid = in_zoneid;
instanceid = in_instanceid;
instanceversion = database.GetInstanceVersion(instanceid);
+ zonetype = database.GetZoneType(in_zoneid, instanceversion);
zonemap = nullptr;
watermap = nullptr;
pathing = nullptr;
diff --git a/zone/zone.h b/zone/zone.h
index f9012b3..db46e17 100644
--- a/zone/zone.h
+++ b/zone/zone.h
@@ -98,12 +98,14 @@ public:
bool SaveZoneCFG();
bool IsLoaded();
bool IsPVPZone() { return pvpzone; }
+ bool IsZoneType(uint8 ztype) { return (ztype == zonetype); }
inline const char* GetLongName() { return long_name; }
inline const char* GetFileName() { return file_name; }
inline const char* GetShortName() { return short_name; }
inline const uint32 GetZoneID() const { return zoneid; }
inline const uint32 GetInstanceID() const { return instanceid; }
inline const uint16 GetInstanceVersion() const { return instanceversion; }
+ inline const uint8 GetZoneType() const { return zonetype; }

inline Timer* GetInstanceTimer() { return Instance_Timer; }

@@ -278,6 +280,7 @@ private:
char* long_name;
char* map_name;
bool pvpzone;
+ uint8 zonetype;
float psafe_x, psafe_y, psafe_z;
uint32 pMaxClients;
bool can_bind;
--
1.8.0.msysgit.0


---

It was easier for me to write the code than to try to explain it all. Sorvani knows how bad I am about rambling :P

Just let me know if you'd like to see this implemented or left in the development stage.


U

Uleat
09-11-2013, 10:55 PM
I ran non-modded code with a modded database last night and discovered that the 'IsMoving' code is probably not needed...

Since bots appear to be point-to-point movers, they will always have a moment of rest..and will cast during this break.

That's the only thing that explains the behavior I saw last night.


My lack of understanding before coding this probably led to my assumption of the frequency of bard songs cast while moving.

(I'll update the branch once I get the ContainerTypes code committed.)

bad_captain
10-11-2013, 10:43 PM
I'm currently looking through the bard spell lists to see if there are any other songs that should be added. I want to keep it to 2 or fewer out of combat songs if possible so that after those songs, the regular combat songs are played. This way, you don't go into battle without the in combat songs, and I was afraid it would play out that the opposite of what we want would happen with a relatively quick pull rate - in combat songs begin playing when entering combat, but do not have time to take affect before the mob dies, then an out of combat song is started, then combat begins again, etc.. While this way doesn't eliminate that possibility, it reduces the chance. There may be 1-2 non-combat songs on while combat starts as well as at least 2 combat songs.

I tested my changes and it seems to work pretty well. I'm debating adding a #bot command to control the option of using out of combat songs, instead of a server-wide rule. It seems like it would be a player preference kind of thing.

Any thoughts?

Uleat
10-11-2013, 11:00 PM
That sounds good! I didn't even consider a command...

Yeah, I had no idea what was feasible for ooc songs..so, I left that to your knowledgeable assessment :D


My original thinking on this was to be able have Selo's without having to play a bard all the time :)

bad_captain
10-11-2013, 11:15 PM
I haven't really seen any more useful out of combat songs. May be some custom ones on servers, but nothing from the default spell list.

I probably will add a command and remove the rule. Or, should we keep it? Will there be server who wouldn't want bard bots running circles around everyone?

bad_captain
10-12-2013, 12:58 AM
I added the command and committed what I had. If I didn't, it could be a week or more before I did. I hate trying to merge in a bunch of changes, so it's out there. Let me know what you think and I can always make changes.

Uleat
10-12-2013, 07:37 PM
I'll have to try it tonight, but the diff's look good!

Thanks again for looking at this :D