Follow along with the video below to see how to install our site as a web app on your home screen.
Note: This feature may not be available in some browsers.
// MQ2Twist.cpp - Bard song twisting plugin for MacroQuest2
//
// koad 03-24-04 Original plugin (http://macroquest.sourceforge.net/phpBB2/viewtopic.php?t=5962&start=2)
// CyberTech 03-31-04 w/ code/ideas from Falco72 & Space-boy
// Cr4zyb4rd 08-19-04 taking over janitorial duties
// Pheph 08-24-04 cleaning up use of MQ2Data
/*
MQ2Twist Version 1.3
Usage:
/twist # # # # # - Twists in the order given.
Valid options are 1 thru 9 for song gems, and 10 thru 19 for item clicks.
These may be mixed in any order, and repeats are allowable. Up to 10 may be
specified.
If a song is specified with a duration longer than standard (ie, selos)
that song will be twisted based on it's duration. For example, riz+mana+selos
would be a 2 song twist with selos pulsed every 2.5 min.
/twist once # # # # # - Twists in the order given, then reverts to original twist
/twist hold <gem #> - Pause twisting and sing only the specified song
/sing <gem#> - alias for /twist hold
/twist stop/end/off - stop twisting, does not clear the twist queue
/stoptwist - alias for above
/twist or /twist start - Resume the twist after using /twist hold or /twist stop
/twist reset - Reset timers for item clicks and long duration songs
/twist delay # - 10ths of a second, minimum of 30, default 33
/twist adjust # - in ticks, how early to recast long duration songs
/twist reload - reload the INI file to update item clicks
/twist slots - List the slots/items defined in the INI and their #'s
/twist quiet - Toggles songs listing and start/stop messages for one-shot twists
----------------------------
Item Click Method:
MQ2Twist uses /itemnotify slotname rightmouseup to perform item clicks.
The INI file allows you to specify items by name (with name=itemname), or by
inventory slot (with slot=slotname). If both a name and slot are defined for an
item, the plugin will attempt to swap the item into that slot (via the /exchange
command) and replace the original item when casting is complete.
The example INI file below contains examples of the types of usage.
----------------------------
Examples:
/twist 1
Sing gem 1 forever
/twist 1 2 3
Twist gems 1,2, and 3 forever
/twist 1 2 3 10
Twist gems 1,2,3, and clicky 10, forever
/twist hold 4 or /sing 4
Sing gem 4 until another singing-related /twist command is given
----------------------------
MQ2Data Variables:
bool Twist Currently Twisting: true/false, if NULL plugin is not loaded
Members:
bool Twisting Currently twisting: true/false.
int Current Returns the curent gem number being sung, -1 for item, or 0 if not twisting
int Next Returns the next gem number to be sung, -1 for item, or 0 if not twistsing
string List Returns the twist sequence in a format suitable for /twist
----------------------------
The ini file has the format:
[MQ2Twist]
Delay=32 Delay between twists. Lag & System dependant.
Adjust=1 This defines how many ticks before the 'normal' recast time to cast a long song.
Long songs are defined as songs greater than 3 ticks in length. If set to 1 tick,
and a song lasts 10 ticks, the song will be recast at the 8 tick mark, instead of
at the 9 tick mark as it normally would.
[Click_10] thru [Click_19]
CastTime=30 Casting Time, -1 to use the normal song delay
ReCastTime=0 How often to recast, 0 to twist normally.
Name="Fife of Battle" Item name for /itemnotify
Slot=neck Slot name for /itemnotify
Delay, CastTime and ReCastTime are specified in 10ths of a
second, so 10 = 1 second, and so on.
INI File Example:
[MQ2Twist]
Delay=31
Quiet=0
;Shadowsong cloak
[Click_10]
CastTime=30
ReCastTime=350
Name=Shadowsong Cloak
Slot=DISABLED
;girdle of living thorns (current belt will be swapped out)
[Click_11]
CastTime=0
ReCastTime=11600
Name=Girdle of Living Thorns
Slot=waist
;nature's melody
[Click_12]
CastTime=-1
ReCastTime=135
Name=DISABLED
Slot=mainhand
;lute of the flowing waters
[Click_13]
CastTime=0
ReCastTime=0
Name=Lute of the Flowing Waters
Slot=DISABLED
[Click_14] ... [Click_19]
CastTime=33
ReCastTime=0
Name=DISABLED
Slot=DISABLED
----------------------------
Changes:
10-05-04
Support "swap in and click" items
09-15-04
Support extra spell slot from Omens of War AA
09-01-04
Command: /twist quiet to toggle some of the spam on/off
Various code fixes/speedups
08-29-04
Moved LONGSONG_ADJUST into INI file and made /twist adjust command to set it on
the fly
08-25-04
Changed output for /twist once to be slightly less misleading
Reset click/song timers every time they're called with /twist hold or /twist once;
if the user's specifying that song, they obviously want to cast it anyway.
Removed the variable MissedNote as close inspection revealed the only place it was
checked for was the line that set it. /boggle
Minor code tweaks, cleanups, formatting changes, etc
08-24-04 (Pheph)
Modified it to use only one TLO, as I found it somewhat messy having 4 different ones.
All the functionality of the old TLO's are now members of ${Twist}
${Twising} is now ${Twist.Twisting}, or just ${Twist}
${TwistCurrent} is now ${Twist.Current}
${TwistNext} is now ${Twist.Next}
${TwistList} is now ${Twist.List}
08-23-04
Reset_ItemClick_Timers was being called far too often. Now the only time we reset
is if a new list of songs are specified. "/twist ${TwistList}" is a useful alias
if you for some reason want the old behavior.
Sing or /twist hold now resets the cast/item timer for that song only, rather than
the entire list.
Command: /twist reset calls Reset_ItemClick_Timers without interfering with the
state of the current twists.
08-22-04
Command: /twist once [songlist] will cycle through the songs entered once, then
revert to the old twist, starting with the song that was interrupted.
Removed command "/twist on", it was making the string compare for "once" annoying,
and I didn't think it was worth the effort for a redundant command.
/twist delay with no argument now returns the delay without resetting it. Values
less than 30 now give a warning...maybe they're not bards or have some other
reason for using a low value.
08-19-04
Minor revamp of item notification. Removed ITEMNOTIFY define and kludged in some
changes from Virtuoso65 to get casting by item name working. /cast is no longer
used.
Added INI file support for above change. File now uses distinct entries for item
names and slots. *Quotes not required for multi-word item names in INI.*
Fixed the MQ2Data value TwistCurrent to display the current song as-advertised, and
added a new value TwistNext with the old behavior of showing the next song in the
queue. (Useful in scripting)
Removed a few DebugSpews that were mega-spamming my debugger output.
CastTime of -1 in the INI file now causes the default delay to be used.
06-01-04
Added LONGSONG_ADJUST (default to 1 tick) to help with the timing of recasting long
songs, such as selo's.
Twisting is now paused when you sit (this would include camping). This fixes
problems reported by Chyld989 (twisting across chars) and Kiniktoo (new autostand on
cast 'feature' in EQ makes twisting funky)
05-19-04
Added workaround for incorrect duration assumption for durationtype=5 songs, such as
Cassindra's Chant of Clarity or Cassindra's Chorus of Clarity.
Added check of char state before casting a song. Actually added for 1.05
Checked states and resulting action are:
Feigned, or Ducking = /stand
Stunned = Delay
Dead - Stop twisting.
If you're a monk using this to click your epic, you'll want to disable the autostand on feign code =)
05-05-05
Fixed CTD on song unmem or death, while twisting. Oops
Removed circle functionality. It's better suited for a plugin like the MQ2MoveUtils
plugin by tonio at http://macroquest.sourceforge.net/phpBB2/viewtopic.php?t=6973
05-01-04
Fixed problem with using pchar before state->ingame causing CTD on eq load (thanks MTBR)
Fixed vc6 compile error w/ reset_itemclick_timers
Replaced various incantations of pChar and pSpawn with GetCharInfo()
Fixed /circle behavior w/ unspecified y/x
Fixed /circle on when already circling and you want to update loc
Added output of parsed circle parameters on start.
04-25-04
Converted to MQ2Data
Top Level Objects:
bool Twisting (if NULL plugin is not loaded)
int TwistCurrent
string TwistList
Removed $Param synatax for above
Added check to make sure item twists specified are defined
Fixed error with twist parameter processing
Changed twist startup output to be more verbose
Command: /twist on added as alias for /twist start
INI File is now named per-character (MQ2Twist_Charname.ini)
* Be sure to rename existing ini files
Modified twist routine to take into account songs with
non-0 recast times or longer than 3 tick durations,
and only re-cast them after the appropriate delay.
This is for songs like Selos 2.5 min duration, etc.
* Note that this makes no attempt to recover if the song
effect is dispelled, your macro will need to take care
of that.
Added ability to compile-time change the method used for
clicking items.
04-13-04
Changed /circle command to allow calling w/o specifying loc
Corrected a problem with multiple consecutive missed notes
Added handling of attempting to sing while stunned
Command: /twist slots, to list the slot to # associations
Command: /twist reload, to reload the ini file on the fly
Command: /twist end, /twist off as aliases for /twist stop
Command: /sing #, as an alias for /twist hold #
Added support for item clickies. Clickies are specified
as "gem" 10-19. For example, /twist 1 2 10 12
Added INI file support for storing item clicky info
and default twist delay.
04-11-04
Integrated the /circle code from Easar, runs in a circle. type
/circle for help.
*/
#include "../MQ2Plugin.h"
PreSetup("MQ2Twist");
typedef struct _ITEMCLICK {
int cast_time;
int recast;
long castdue;
int disabled;
int nousename;
CHAR slot[MAX_STRING];
CHAR name[MAX_STRING];
} ITEMCLICK;
int MQ2TwistEnabled = 0;
const int MAX_SONG=10;
int LONGSONG_ADJUST=1; // In TICKS, not seconds. Used for long songs (greater than 3 ticks in duration). See docs.
int CAST_TIME=33;
int NumSongs=0;
int AltNumSongs=0;
int Song[MAX_SONG*2];
int AltSong[MAX_SONG*2];
long SongNextCast[MAX_SONG*2];
ITEMCLICK ItemClick[MAX_SONG];
int CurrSong=0;
int AltCurrSong=0;
int PrevSong=0;
int HoldSong=0;
long CastDue=0;
bool bTwist=false;
bool altTwist=false;
bool quiet;
CHAR SwappedOutItem[MAX_STRING];
CHAR SwappedOutSlot[MAX_STRING];
long GetTime();
VOID TwistCommand(PSPAWNINFO pChar, PCHAR szLine);
VOID StopTwistCommand(PSPAWNINFO pChar, PCHAR szLine);
VOID SingCommand(PSPAWNINFO pChar, PCHAR szLine);
BOOL dataTwist(PCHAR szIndex, MQ2TYPEVAR &Ret);
CHAR MQ2TwistTypeTemp[MAX_STRING]={0};
//get current timestamp in tenths of a second
long GetTime()
{
SYSTEMTIME st;
::GetSystemTime(&st);
long lCurrent=0;
lCurrent = st.wDay * 24 * 60 * 60 * 10;
lCurrent += st.wHour * 60 * 60 * 10;
lCurrent += st.wMinute * 60 * 10;
lCurrent += st.wSecond * 10;
lCurrent += (long)(st.wMilliseconds/100);
return (lCurrent);
}
VOID MQ2TwistDoCommand(PSPAWNINFO pChar, PCHAR szLine)
{
HideDoCommand(pChar, szLine, FromPlugin);
}
VOID DoSwapOut()
{
CHAR szTemp[MAX_STRING];
if (SwappedOutItem[0]) {
sprintf(szTemp,"/exchange \"%s\" %s",SwappedOutItem,SwappedOutSlot);
MQ2TwistDoCommand(NULL, szTemp);
SwappedOutItem[0]=0;
}
}
VOID DoSwapIn(int Index)
{
CHAR szTemp[MAX_STRING];
if (strnicmp(ItemClick[Index].slot,"DISABLED",8)) {
sprintf(szTemp,"${InvSlot[%s].Item.Name}",ItemClick[Index].slot);
ParseMacroData(szTemp);
strcpy(SwappedOutItem,szTemp);
strcpy(SwappedOutSlot,ItemClick[Index].slot);
sprintf(szTemp,"/exchange \"%s\" %s",ItemClick[Index].name,ItemClick[Index].slot);
MQ2TwistDoCommand(NULL, szTemp);
}
}
VOID Reset_ItemClick_Timers()
{
int i;
for (i=0;i<10;i++) {
ItemClick.castdue = 0;
}
for (i=0;i<MAX_SONG*2;i++) {
SongNextCast = 0;
}
}
VOID Update_INIFileName() {
if (GetCharInfo()) {
sprintf(INIFileName,"%s\\MQ2Twist_%s.ini",gszINIPath,GetCharInfo()->Name);
} else {
sprintf(INIFileName,"%s\\MQ2Twist.ini",gszINIPath);
}
}
VOID Load_MQ2Twist_INI()
{
CHAR szTemp[MAX_STRING]={0};
CHAR szSection[MAX_STRING]={0};
Update_INIFileName();
CAST_TIME = GetPrivateProfileInt("MQ2Twist","Delay",33,INIFileName);
sprintf(szTemp, "%d", CAST_TIME);
WritePrivateProfileString("MQ2Twist","Delay",szTemp,INIFileName);
quiet = GetPrivateProfileInt("MQ2Twist","Quiet",0,INIFileName)? 1 : 0;
sprintf(szTemp, "%d", quiet);
WritePrivateProfileString("MQ2Twist","Quiet",szTemp,INIFileName);
LONGSONG_ADJUST = GetPrivateProfileInt("MQ2Twist","Adjust",1,INIFileName);
sprintf(szTemp, "%d", LONGSONG_ADJUST);
WritePrivateProfileString("MQ2Twist","Adjust",szTemp,INIFileName);
for (int i=0;i<10;i++) {
sprintf(szSection, "Click_%d", i+10);
ItemClick.cast_time = GetPrivateProfileInt(szSection,"CastTime",0,INIFileName);
ItemClick.recast = GetPrivateProfileInt(szSection,"ReCastTime",0,INIFileName);
GetPrivateProfileString(szSection,"Name","DISABLED",ItemClick.name,MAX_STRING,INIFileName);
GetPrivateProfileString(szSection,"Slot","DISABLED",ItemClick.slot,MAX_STRING,INIFileName);
if(!strnicmp("DISABLED", ItemClick.name, 8)) {
if (!strnicmp("DISABLED", ItemClick.slot, 8)) {
ItemClick.disabled = true;
DebugSpew("MQ2Twist: Slot %d disabled",i+1);
} else {
ItemClick.nousename = true;
ItemClick.disabled = false;
}
} else ItemClick.disabled = false;
// Write the values above back to disk, mostly to initialize it for easy editing.
sprintf(szTemp, "%d", ItemClick.cast_time);
WritePrivateProfileString(szSection,"CastTime",szTemp,INIFileName);
// If the CastTime is set to -1 in the INI file, use the default.
ItemClick.cast_time = ItemClick.cast_time==-1 ? CAST_TIME : ItemClick.cast_time;
sprintf(szTemp, "%d", ItemClick.recast);
WritePrivateProfileString(szSection,"ReCastTime",szTemp,INIFileName);
WritePrivateProfileString(szSection,"Name",ItemClick.name,INIFileName);
WritePrivateProfileString(szSection,"Slot",ItemClick.slot,INIFileName);
DebugSpewAlways("Initializing MQ2Twist: Processed %s", szSection);
}
}
VOID SingCommand(PSPAWNINFO pChar, PCHAR szLine)
{
CHAR szTemp[MAX_STRING]={0};
CHAR szMsg[MAX_STRING]={0};
int i;
GetArg(szTemp,szLine,1);
i=atoi(szTemp);
if (i>=1 && i<=19) { // valid range?
HoldSong = i;
bTwist=true;
CastDue = -1;
sprintf(szMsg, "MQ2Twist::Holding Twist and casting gem %d", HoldSong);
WriteChatColor(szMsg,USERCOLOR_DEFAULT);
MQ2TwistDoCommand(pChar,"/stopsong");
if (i>10) { //item?
ItemClick[i-10].castdue = 0;
} else SongNextCast = 0; //nope, song
} else WriteChatColor("MQ2Twist::Invalid gem specified, ignoring",USERCOLOR_DEFAULT);
}
VOID StopTwistCommand(PSPAWNINFO pChar, PCHAR szLine)
{
bTwist=false;
HoldSong=0;
MQ2TwistDoCommand(pChar,"/stopsong");
WriteChatColor("MQ2Twist::Stopping Twist",USERCOLOR_DEFAULT);
}
VOID PrepNextSong() {
if (CurrSong>NumSongs) {
if (altTwist) {
NumSongs=AltNumSongs;
CurrSong=PrevSong=AltCurrSong;
for (int i=0; i<NumSongs; i++) Song=AltSong;
altTwist=false;
if (!quiet) WriteChatColor("MQ2Twist::One-shot twist ended, normal twist will resume next pulse",USERCOLOR_DEFAULT);
} else CurrSong=1;
}
}
VOID DisplayTwistHelp() {
WriteChatColor("MQ2Twist - Twist song or songs",USERCOLOR_DEFAULT);
WriteChatColor("Usage: /twist <gem#> - Twists in the order given.",USERCOLOR_DEFAULT);
WriteChatColor(" Valid options are 1 thru 9 for song gems, and 10 thru 19 for item clicks.",USERCOLOR_DEFAULT);
WriteChatColor(" These may be mixed in any order, and repeats are allowable.",USERCOLOR_DEFAULT);
WriteChatColor("Usage: /twist hold <gem #> - Pause twisting and sing only the specified song",USERCOLOR_DEFAULT);
WriteChatColor(" /sing <gem#> - alias for /twist hold",USERCOLOR_DEFAULT);
WriteChatColor("Usage: /twist once <gem#> Twists once in the order given, then reverts to original twist",USERCOLOR_DEFAULT);
WriteChatColor("Usage: /twist or /twist start - Resume the twist after using /twist hold or /twist stop",USERCOLOR_DEFAULT);
WriteChatColor("Usage: /twist reset - Reset timers for item clicks and long duration songs",USERCOLOR_DEFAULT);
WriteChatColor("Usage: /twist delay # - 10ths of a second, minimum of 30, default 33",USERCOLOR_DEFAULT);
WriteChatColor("Usage: /twist adjust # - in ticks, how early to recast long duration songs",USERCOLOR_DEFAULT);
WriteChatColor("Usage: /twist stop/end/off - stop twisting, does not clear the twist queue",USERCOLOR_DEFAULT);
WriteChatColor(" /stoptwist - alias for /twist stop",USERCOLOR_DEFAULT);
WriteChatColor("Usage: /twist reload - reload the INI file to update item clicks",USERCOLOR_DEFAULT);
WriteChatColor("Usage: /twist slots - List the slots defined in the INI and their #'s",USERCOLOR_DEFAULT);
}
// ***************************************************************************
// Function: TwistCommand
// Description: Our /twist command. sing for me!
// ***************************************************************************
VOID TwistCommand(PSPAWNINFO pChar, PCHAR szLine)
{
CHAR szTemp[MAX_STRING]={0};
CHAR szMsg[MAX_STRING]={0};
CHAR szChat[MAX_STRING]={0};
PSPELL pSpell;
int i;
GetArg(szTemp,szLine,1);
if (NumSongs && (!strlen(szTemp) || !strnicmp(szTemp,"start", 5))) {
WriteChatColor("MQ2Twist::Starting Twist",USERCOLOR_DEFAULT);
DoSwapOut();
bTwist=true;
HoldSong=0;
CastDue = -1;
return;
}
if (!strnicmp(szTemp,"stop", 4) || !strnicmp(szTemp,"end", 3) || !strnicmp(szTemp,"off", 3)) {
DoSwapOut();
StopTwistCommand(pChar, szTemp);
return;
}
if (!strnicmp(szTemp,"slots", 5)) {
WriteChatColor("MQ2Twist 'Song' Numbers for right click effects:",USERCOLOR_DEFAULT);
for (i=0;i<10;i++) {
if (ItemClick.disabled) break;
if (ItemClick.nousename) {
sprintf(szMsg, " %d = %s (slot)", i+10, ItemClick.slot);
} else {
sprintf(szMsg, " %d = %s (name) %s (slot)", i+10, ItemClick.name, ItemClick.slot);
}
WriteChatColor(szMsg,USERCOLOR_DEFAULT);
}
WriteChatColor("---",USERCOLOR_DEFAULT);
return;
}
if (!strnicmp(szTemp,"reload", 6)) {
WriteChatColor("MQ2Twist::Re-Loading INI Values",USERCOLOR_DEFAULT);
Load_MQ2Twist_INI();
return;
}
if (!strnicmp(szTemp,"delay", 5)) {
GetArg(szTemp,szLine,2);
if (strlen(szTemp)>0) {
i=atoi(szTemp);
if (i<=30) {
WriteChatColor("MQ2Twist::WARNING delay specified is less than standard song cast time",CONCOLOR_RED);
}
CAST_TIME=i;
Update_INIFileName();
WritePrivateProfileString("MQ2Twist","Delay",itoa(CAST_TIME, szTemp, 10),INIFileName);
sprintf(szMsg, "MQ2Twist::Set delay to %d, INI updated", CAST_TIME);
} else sprintf(szMsg, "MQ2Twist::Delay %d", CAST_TIME);
WriteChatColor(szMsg,USERCOLOR_DEFAULT);
return;
}
if (!strnicmp(szTemp,"quiet", 5)) {
quiet=!quiet;
sprintf(szTemp,"%d",quiet);
WritePrivateProfileString("MQ2Twist","Quiet",szTemp,INIFileName);
sprintf(szMsg,"MQ2Twist::Now being %s",quiet ? "quiet" : "noisy");
WriteChatColor(szMsg,USERCOLOR_DEFAULT);
return;
}
if (!strnicmp(szTemp,"adjust", 6)) {
GetArg(szTemp,szLine,2);
if (strlen(szTemp)>0) {
i=atoi(szTemp);
LONGSONG_ADJUST=i;
Update_INIFileName();
WritePrivateProfileString("MQ2Twist","Adjust",itoa(LONGSONG_ADJUST, szTemp, 10),INIFileName);
sprintf(szMsg, "MQ2Twist::Long song adjustment set to %d, INI updated", LONGSONG_ADJUST);
} else sprintf(szMsg, "MQ2Twist::Long song adjustment: %d", LONGSONG_ADJUST);
WriteChatColor(szMsg,USERCOLOR_DEFAULT);
return;
}
if (!strnicmp(szTemp,"hold", 4)) {
GetArg(szTemp,szLine,2);
SingCommand(pChar, szTemp);
return;
}
if (!strnicmp(szTemp,"reset", 5)) {
Reset_ItemClick_Timers();
WriteChatColor("MQ2Twist::Timers reset",CONCOLOR_YELLOW);
return;
}
// check help arg, or display if we have no songs defined and /twist was used
if (!strlen(szTemp) || !strnicmp(szTemp,"help", 4)) {
DisplayTwistHelp();
return;
}
// if we are "one-shot twisting", save the current song array and current song
if (!strnicmp(szTemp,"once", 4)) {
WriteChatColor("MQ2Twist one-shot twisting:",CONCOLOR_YELLOW);
if (altTwist) {
CurrSong=NumSongs+1;
PrepNextSong(); // If CurrSong > NumSongs relaod the song list
}
if (NumSongs) {
AltNumSongs=NumSongs;
AltCurrSong=CurrSong;
for (i=0; i<NumSongs; i++) AltSong=Song;
}
altTwist=true;
} else altTwist=false;
DoSwapOut();
DebugSpew("MQ2Twist::TwistCommand Parsing twist order");
NumSongs=0;
HoldSong=0;
if (!altTwist) {
if (!quiet) {
WriteChatColor("MQ2Twist Twisting:",CONCOLOR_YELLOW);
} else WriteChatColor("MQ2Twist::Starting Twist",USERCOLOR_DEFAULT);
}
for (i=0 + altTwist ? 1 : 0; i<20; i++)
{
GetArg(szTemp,szLine,i+1);
if (!strlen(szTemp)) break;
Song[NumSongs]=atoi(szTemp);
if (Song[NumSongs]>=1 && Song[NumSongs]<=19) {
if ((Song[NumSongs]>9) && ItemClick[Song[NumSongs]-10].disabled) {
sprintf(szChat, " Undefined item specified (%s) - ignoring (see INI file)", szTemp);
WriteChatColor(szChat,CONCOLOR_RED);
} else {
sprintf(szMsg, " %s - ", szTemp);
if (Song[NumSongs]<=9) {
pSpell=GetSpellByID(GetCharInfo2()->MemorizedSpells[Song[NumSongs]-1]);
if (altTwist) SongNextCast[NumSongs] = 0;
if (pSpell) strcat(szMsg, pSpell->Name);
} else {
if (ItemClick[Song[NumSongs]-10].nousename) {
strcat(szMsg, ItemClick[Song[NumSongs]-10].slot);
} else {
strcat(szMsg, ItemClick[Song[NumSongs]-10].name);
}
if (altTwist) ItemClick[NumSongs].castdue = 0;
}
if (!quiet) WriteChatColor(szMsg,COLOR_LIGHTGREY);
NumSongs++;
}
} else {
sprintf(szChat, " Invalid gem specified (%s) - ignoring", szTemp);
WriteChatColor(szChat,CONCOLOR_RED);
}
}
sprintf(szTemp, "Twisting %d song%s", NumSongs, NumSongs>1 ? "s" : "");
if (!quiet) WriteChatColor(szTemp,CONCOLOR_YELLOW);
if (NumSongs>0) bTwist=true;
CurrSong = 1;
PrevSong = 1;
CastDue = -1;
MQ2TwistDoCommand(pChar,"/stopsong");
if (!altTwist) Reset_ItemClick_Timers();
}
/*
Checks to see if character is in a fit state to cast next song/item
Note 1: Do not try to correct SIT state, or you will have to stop the
twist before re-memming songs
Note 2: Since the auto-stand-on-cast bullcrap added to EQ a few patches ago,
chars would stand up every time it tried to twist a song. So now
we stop twisting at sit.
*/
BOOL CheckCharState() {
if (!bTwist) return FALSE;
if (GetCharInfo()) {
if (GetCharInfo()->Stunned==1) return FALSE;
switch (GetCharInfo()->standstate) {
case STANDSTATE_SIT:
WriteChatColor("MQ2Twist::Stopping Twist",USERCOLOR_DEFAULT);
bTwist = FALSE;
return FALSE;
break;
case STANDSTATE_FEIGN:
MQ2TwistDoCommand(NULL,"/stand");
return FALSE;
break;
case STANDSTATE_DEAD:
WriteChatColor("MQ2Twist::Stopping Twist",USERCOLOR_DEFAULT);
bTwist = FALSE;
return FALSE;
break;
default:
break;
}
}
if (pCastingWnd) {
PCSIDLWND pCastingWindow = (PCSIDLWND)pCastingWnd;
if (pCastingWindow->Show == 1) return FALSE;
// Don't try to twist if the casting window is up, it implies the previous song
// is still casting, or the user is manually casting a song between our twists
}
return TRUE;
}
class MQ2TwistType *pTwistType=0;
class MQ2TwistType : public MQ2Type
{
public:
enum TwistMembers
{
Twisting=1,
Next=2,
Current=3,
List=4,
};
MQ2TwistType():MQ2Type("twist")
{
TypeMember(Twisting);
TypeMember(Next);
TypeMember(Current);
TypeMember(List);
}
~MQ2TwistType()
{
}
bool GetMember(MQ2VARPTR VarPtr, PCHAR Member, PCHAR Index, MQ2TYPEVAR &Dest)
{
PMQ2TYPEMEMBER pMember=MQ2TwistType::FindMember(Member);
if (!pMember)
return false;
switch((TwistMembers)pMember->ID)
{
case Twisting:
/* Returns: bool
0 - Not Twisting
1 - Twisting
*/
Dest.Int=bTwist;
Dest.Type=pBoolType;
return true;
case Next:
/* Returns: int
0 - Not Twisting
-1 - Casting Item
1-9 - Current Gem
*/
Dest.Int=HoldSong ? HoldSong : Song[CurrSong-1];
if (Dest.Int>9) Dest.Int = -1;
if (!bTwist) Dest.Int = 0;
Dest.Type=pIntType;
return true;
case Current:
Dest.Int=HoldSong ? HoldSong : Song[PrevSong-1];
if (Dest.Int>9) Dest.Int = -1;
if (!bTwist) Dest.Int = 0;
Dest.Type=pIntType;
return true;
case List:
/* Returns: string
Space separated list of gem and item #'s being twisted, in order
*/
int a;
CHAR szTemp[MAX_STRING] = {0};
MQ2TwistTypeTemp[0] = 0;
for (a=0; a<NumSongs; a++) {
sprintf(szTemp, "%d ", Song[a]);
strcat(MQ2TwistTypeTemp, szTemp);
}
Dest.Ptr=&MQ2TwistTypeTemp[0];
Dest.Type=pStringType;
return true;
}
return false;
}
bool ToString(MQ2VARPTR VarPtr, PCHAR Destination)
{
if (bTwist)
strcpy(Destination,"TRUE");
else
strcpy(Destination,"FALSE");
return true;
}
bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source)
{
return false;
}
bool FromString(MQ2VARPTR &VarPtr, PCHAR Source)
{
return false;
}
};
BOOL dataTwist(PCHAR szName, MQ2TYPEVAR &Dest)
{
Dest.DWord=1;
Dest.Type=pTwistType;
return true;
}
// ******************************
// **** MQ2 API Calls Follow ****
// ******************************
PLUGIN_API VOID InitializePlugin(VOID)
{
DebugSpewAlways("Initializing MQ2Twist");
AddCommand("/twist",TwistCommand,0,1,1);
AddCommand("/sing",SingCommand,0,1,1);
AddCommand("/stoptwist",StopTwistCommand,0,0,1);;
AddMQ2Data("Twist",dataTwist);
pTwistType = new MQ2TwistType;
}
PLUGIN_API VOID ShutdownPlugin(VOID)
{
DebugSpewAlways("MQ2Twist::Shutting down");
RemoveCommand("/twist");
RemoveCommand("/sing");
RemoveCommand("/stoptwist");
RemoveMQ2Data("Twist");
delete pTwistType;
}
PLUGIN_API VOID OnPulse(VOID)
{
CHAR szTemp[MAX_STRING] = {0};
PSPELL pSpell;
int a,b;
if (!MQ2TwistEnabled || !CheckCharState()) return;
if ((HoldSong>0) || ((NumSongs==1) && !altTwist)) {
// DebugSpew("MQ2Twist::Pulse - Single Song");
if ( CastDue<0 || ( ((CastDue-GetTime()) <= 0 ) && (GetCharInfo()->pSpawn->CastingSpellID == -1) ) ) {
int SongTodo = HoldSong ? HoldSong : Song[0];
if (SongTodo <= 9) {
DebugSpew("MQ2Twist::Pulse - Single Song (Casting Gem %d)", SongTodo);
sprintf(szTemp,"/multiline ; /stopsong ; /cast %d", SongTodo);
MQ2TwistDoCommand(NULL,szTemp);
CastDue = GetTime()+CAST_TIME;
} else {
if (ItemClick[SongTodo-10].castdue-GetTime() <= 0) {
if (ItemClick[SongTodo-10].nousename) {
DebugSpew("MQ2Twist::Pulse - Single Song (Casting Item %d - %s)", SongTodo, ItemClick[SongTodo-10].slot);
sprintf(szTemp,"/multiline ; /stopsong ; /itemnotify %s rightmouseup", ItemClick[SongTodo-10].slot);
} else {
DebugSpew("MQ2Twist::Pulse - Single Song (Casting Item %d - %s)", SongTodo, ItemClick[SongTodo-10].name);
DoSwapIn(SongTodo-10);
sprintf(szTemp,"/multiline ; /stopsong ; /itemnotify ${FindItem[%s].InvSlot.Name} rightmouseup", ItemClick[SongTodo-10].name);
}
MQ2TwistDoCommand(NULL,szTemp);
ItemClick[SongTodo-10].castdue = ItemClick[SongTodo-10].recast ? (GetTime()+ItemClick[SongTodo-10].cast_time+ItemClick[SongTodo-10].recast) : (GetTime()+CAST_TIME);
CastDue = ItemClick[SongTodo-10].castdue;
}
}
}
} else {
int SongTodo = Song[CurrSong-1];
if (NumSongs && ((CastDue-GetTime()) <= 0)) {
DoSwapOut();
if (SongTodo <= 9) {
if (SongNextCast[CurrSong-1]-GetTime() <= 0) {
DebugSpew("MQ2Twist::OnPulse - Next Song = %s", szTemp);
sprintf(szTemp,"/multiline ; /stopsong ; /cast %d", SongTodo);
MQ2TwistDoCommand(NULL,szTemp);
pSpell=GetSpellByID(GetCharInfo2()->MemorizedSpells[Song[CurrSong-1]-1]);
if(!pSpell) {
WriteChatColor("Songs not present - suspending twist. /twist to resume",CONCOLOR_RED);
bTwist = FALSE;
return;
}
a = pSpell->RecastTime/100; // recasttime in 10's of a second
b = GetSpellDuration(pSpell,GetCharInfo()->pSpawn) * 60; // duration in 10's of a second
if (pSpell->DurationType == 5 && !pSpell->DurationValue1) {
b = 18; //FIXME - Remove once GetSpellDuration handles duration type5
}
CastDue = GetTime()+CAST_TIME;
// duration > 18 secs
if (b > 180) SongNextCast[CurrSong-1] = CastDue + b - (LONGSONG_ADJUST*60);
// recast > 0 secs
else if (a > 0) SongNextCast[CurrSong-1] = CastDue + a;
// normal
else SongNextCast[CurrSong-1] = CastDue;
PrevSong=CurrSong;
} // if it's not time for currsong to be re-sung, skip it in the twist
CurrSong++;
PrepNextSong();
} else {
if (ItemClick[SongTodo-10].castdue-GetTime() <= 0) {
if (ItemClick[SongTodo-10].nousename) {
DebugSpew("MQ2Twist::Pulse - Next Song (Casting Slot %d - %s)", SongTodo, ItemClick[SongTodo-10].slot);
sprintf(szTemp,"/multiline ; /stopsong ; /itemnotify %s rightmouseup", ItemClick[SongTodo-10].slot);
} else {
DebugSpew("MQ2Twist::Pulse - Next Song (Casting Item %d - %s)", SongTodo, ItemClick[SongTodo-10].name);
DoSwapIn(SongTodo-10);
sprintf(szTemp,"/multiline ; /stopsong ; /itemnotify ${FindItem[%s].InvSlot.Name} rightmouseup", ItemClick[SongTodo-10].name);
}
MQ2TwistDoCommand(NULL,szTemp);
ItemClick[SongTodo-10].castdue = ItemClick[SongTodo-10].recast ? (GetTime()+ItemClick[SongTodo-10].cast_time+ItemClick[SongTodo-10].recast) : (GetTime()+CAST_TIME);
CastDue = GetTime()+ItemClick[SongTodo-10].cast_time;
}
PrevSong=CurrSong; // Increment twist position even if we didn't do an itemnotify - this might have a long recast
CurrSong++; // interval set, and we just skip it until it's time to recast, rather than keep a separate timer.
PrepNextSong();
}
}
}
}
PLUGIN_API DWORD OnIncomingChat(PCHAR Line, DWORD Color)
{
if (!bTwist || !MQ2TwistEnabled) return 0;
// DebugSpew("MQ2Twist::OnIncomingChat(%s)",Line);
if ( !strcmp(Line,"You miss a note, bringing your song to a close!") ||
!strcmp(Line,"You haven't recovered yet...") ||
!strcmp(Line,"Your spell is interrupted.") ) {
DebugSpew("MQ2Twist::OnIncomingChat - Song Interrupt Event");
if (!HoldSong) CurrSong=PrevSong;
CastDue = -1;
SongNextCast[CurrSong-1] = -1;
return 0;
}
if (!strcmp(Line,"You can't cast spells while stunned!") ) {
DebugSpew("MQ2Twist::OnIncomingChat - Song Interrupt Event (stun)");
if (!HoldSong) CurrSong=PrevSong;
CastDue = GetTime() + 10;
// Wait one second before trying again, to avoid spamming the trigger text w/ cast attempts
return 0;
}
return 0;
}
PLUGIN_API VOID SetGameState(DWORD GameState)
{
DebugSpew("MQ2Twist::SetGameState()");
if (GameState==GAMESTATE_INGAME)
{
MQ2TwistEnabled = true;
Load_MQ2Twist_INI();
} else {
MQ2TwistEnabled = false;
}
}
PLUGIN_API VOID OnPulse(VOID)
{
CHAR szTemp[MAX_STRING] = {0};
PSPELL pSpell;
int a,b;
if (IsWindowOpen("RespawnWnd")) {
DoSwapOut();
bTwist=false;
HoldSong=0;
MQ2TwistDoCommand(pChar,"/stopsong");
WriteChatColor("You died! Stopping twist!",USERCOLOR_DEFAULT);
return;
}
if (!MQ2TwistEnabled || !CheckCharState()) return;
if ((HoldSong>0) || ((NumSongs==1) && !altTwist)) {
It wasn't. Sorry about that. The good news is I released a new RedQuest compile a few minutes ago that contains this MQ2Twist fix. Thanks for bringing it to our attention and sorry we didnt get to it sooner.Sum1 said:Was this the version of MQ2Twist used in the redguides compile this time around?
k, one more question
i want selos to be cast each 9 sec, how do i do that? i want it to stop twisting those song i have on 1 twisting macro and then cast selo before i goes out
/twist # # # # #
Twists the songs/items in the order specified (up to 10 can be specified).
Valid options are 1 thru NUM_SPELL_GEMS (EQData.h) for song gems, and 21 thru 29 for item clicks. These may be mixed in any order, and repeats are allowable. If a song is specified with a duration longer than standard (eg. Selo's Accelerating Chorus), that song will be twisted in based on it's duration. For example, riz+mana+selos
Rich (BB code):
would be a 2 song twist with selos pulsed every 2.5 min.
/twist once # # # # #
Twists in songs once the order given, then reverts back to original twist
/twist hold #
Pause twisting and sing only the specified song
/twist stop/end/off
Stop twisting, does not clear the twist queue
/twist or /twist start
Resume the twist after using /twist hold or /twist stop
/twist reset
Reset timers for item clicks and long duration songs
/twist delay #
Specify the delay to be used before starting a new song (in 10ths of a second). The minimum is 30, default is 33
/twist adjust #
How early to recast long duration songs (in ticks)
/twist reload
Reload the INI file to update item clicks
/twist slots
List the slots/items defined in the INI and their item numbers
/twist quiet
Toggles songs listing and start/stop messages for one-shot twists
/sing
Alias for /twist hold
/stoptwist
Alias for /twist stop
/twist 1 Sing gem 1 forever
/twist 1 2 3 Twist gems 1,2, and 3 forever
/twist 1 2 3 10 Twist gems 1,2,3, and clicky 10, forever
/twist hold 4 Sing gem 4 forever, until another singing-related /twist command is given
/sing 4 Same as above
Set Click_16 to CastTime=32, ReCastTime=120, Name="Cassindra's Chorus of Clarity", Slot=AA ; and save to INI
[MQ2Twist]
Delay=32 Delay between twists (in 10ths of a second). Lag & System dependant.
Adjust=1 This defines how many ticks before the 'normal'
recast time to cast a long song.
[Click_21] through [Click_29]
CastTime=30 Casting Time, -1 to use the normal song delay
ReCastTime=0 How often to recast, 0 to twist normally.
Name="Fife of Battle" Item name for /itemnotify
Slot=neck Slot name for /itemnotify
MQ2Twist uses /itemnotify slotname rightmouseup to perform item clicks.
The INI file allows you to specify items by name (with name=itemname), or by
inventory slot (with slot=slotname). If both a name and slot are defined for an
item, the plugin will attempt to swap the item into that slot (via the /exchange
command) and replace the original item when casting is complete.
The example INI file below contains examples of the types of usage.
[MQ2Twist]
Delay=31
Quiet=0
;Shadowsong cloak
[Click_21]
CastTime=30
ReCastTime=350
Name=Shadowsong Cloak
Slot=DISABLED
;girdle of living thorns (current belt will be swapped out)
[Click_22]
CastTime=0
ReCastTime=11600
Name=Girdle of Living Thorns
Slot=waist
;nature's melody
[Click_23]
CastTime=-1
ReCastTime=135
Name=DISABLED
Slot=mainhand
;lute of the flowing waters
[Click_24]
CastTime=0
ReCastTime=0
Name=Lute of the Flowing Waters
Slot=DISABLED
;lute of the flowing waters
[Click_25]
CastTime=32
ReCastTime=120
Name=Cassindra's Chorus of Clarity
Slot=AA
[Click_26] ... [Click_30]
CastTime=33
ReCastTime=0
Name=DISABLED
Slot=DISABLED
- MQ2Twist Problem when casting items. When using /twist # # # # and you issue a /twist off command and then use an /useitem SomeItem command. MQ2Twist interrupts the casting of the item and primarily issues this command. MQ2Twist::One-shot twist ended, normal twist will resume next pulse. It will also interrupt casting Items with just an Interrupted message.
- Running kissassist Set up a bard with a normal twist and in the buffs section setup to cast Songblade of the Eternal. MQ2Twist will continually interrupt the casting of the item even though twist is off and the /useitem command is being used outside to twist.