// TMW2 scripts.
// Authors:
// Jesusalva
// TMW Org.
// Description:
// HUB functions (Login, Logout, Death)
// HUB_Login ()
function script HUB_Login {
getmapxy(.@mapa$, .@a,.@b, 0);
// Login on Blue Sage Workshop/Library
if (.@mapa$ == "020-7-1") {
addtimer(1000, "#BlueSageHUB::OnCycle");
}
// Random Treasure cleaning
if (CHAREG_CLEANUP < gettimetick(2)-CHEST_WAITTIME)
deletearray RNGTREASURE_DATE;
// PVP Cooldown cleaning
if (CHAREG_CLEANUP < gettimetick(2)-PVP_WAITTIME)
deletearray PVP_COOLDOWN;
return;
}
// HUB_Logout ( {dead} )
function script HUB_Logout {
.@dead=getarg(0, false);
getmapxy(.@mapa$, .@a,.@b, 0);
.@zone$=getmapinfo(MAPINFO_ZONE, .@mapa$);
// Hardcore Server
if ($HARDCORE && .@dead) {
// Update Absolutions
if (ABSOLVE_DAY != gettimeparam(GETTIME_DAYOFMONTH)) {
ABSOLVE_DAY=gettimeparam(GETTIME_DAYOFMONTH);
ABSOLVE_CNT=0;
}
// Main Loop
if (@grace) {
// Grace is upon you (ie. script death)
@grace=false;
} else if (.@zone$ == "MMO" && ABSOLVE_CNT <= 3) {
// Absolve (limited attempts)
ABSOLVE_CNT+=1;
dispbottom l("This is a special map so your death is not counted.");
dispbottom b(l("You have %d non-counting deaths remaining today.", 3-ABSOLVE_CNT));
if (ABSOLVE_CNT == 3)
dispbottom col(l("WARNING: if you die again today in a special map it will be PERMANENT."), 1);
} else {
// Meet your final demise!
atcommand("@dropall");
makeitem CoinBag, Zeny/500, .@mapa$, .@a, .@b;
Zeny=0;
//resetlvl(2); // FIXME: Split the exp
// TODO: Warp back to Candor or it'll be unplayable
// TODO: It could be @jail, but it is buggy
atcommand("@jailfor 1d "+strcharinfo(0));
}
// Vanished on Cindy Cave
} else if (.@mapa$ == "021-4" && strcharinfo(0) == $@CINDY_HERO$) {
donpcevent("Cindy#Outside::OnReckless");
recovery(getcharid(3));
warp any("010-1", "010-2"), 0, 0;
sc_start2 SC_POISON, 1, 90, 10000;
die();
if (!$HARDCORE) heal -1, -1;
} else if (.@mapa$ == "021-4") {
.@pl = getmapusers("021-4")-1;
if (.@pl < 1)
donpcevent("Cindy#Outside::OnCleanUp");
recovery(getcharid(3));
warp "Save", 0, 0;
}
// Logout while donating blood
if (getq(HurnscaldQuest_BloodDonor) == 2) {
slide 35, 28;
setpcblock(PCBLOCK_ATTACK|PCBLOCK_SKILL|PCBLOCK_USEITEM|PCBLOCK_MOVE, false);
setq HurnscaldQuest_BloodDonor, 0, gettimetick(2)+3600; // one hour penalty
}
// Logout/Death on Nard's ship hold
if (compare(.@mapa$, "002-2") || compare(.@mapa$,"nard")) {
setq2 ShipQuests_Peter, 0;
setq3 ShipQuests_Peter, -1;
}
// Logout on botcheck area
if (compare(.@mapa$,"botcheck") && !.@dead) {
if (!is_staff())
atcommand "@jail "+strcharinfo(0);
}
// Died on Terranite Cave where exp penalty is lower
if (.@mapa$ == "015-6" && .@dead) {
@deathpenalty_override=2;
@deathpenalty_realvalue=readparam(BaseExp);
@deathpenalty_realvaljob=readparam(JobExp);
}
// Died or logged out at Tulimshar Arena (FIXME)
if (compare(.@mapa$, "ARENA")) {
if ('UDTf) {
'UDTf=0;
killmonster(getmap(), instance_npcname("Arnea#003-13")+"::OnGladius");
deltimer(instance_npcname("Arnea#003-13")+"::OnVerify");
}
}
// Died or logged out at Candor Battle
if (.@mapa$ == "006-1") {
if (@crazypoints > CRAZYPOINTS) {
CRAZYPOINTS=@crazypoints;
dispbottom l("Crazyfefe Cave: New Highscore: @@ points", CRAZYPOINTS);
@crazypoints=0;
}
}
// Died or logged during Bandit Lord fight
if (.@mapa$ == "015-2") {
if (isin("015-2", 228, 227, 282, 280))
killmonster("015-2", "#BanditLordDen::OnLordDeath");
}
// Died or logged out on Blue Sage House
if (.@mapa$ == "020-7-1") {
callfunc("BSClearNest", @nestid);
}
// Died or logged out on Player Story 2 - Magic School Port
if (compare(.@mapa$, "0030")) {
if (@ASSASSIN > 0)
delcells "MQ2Wall"+getcharid(0);
killmonsterall(getmap());
}
// Died or logged out on Player Story 5 - Forgotten Throne Room
if (compare(.@mapa$, "hmc")) {
if (@instid > 0)
.@n$=instance_npcname("#Core02331", @instid);
else
.@n$=instance_npcname("#Core02331");
deltimer(.@n$+"::OnW01");
deltimer(.@n$+"::OnW02");
deltimer(.@n$+"::OnE07");
deltimer(.@n$+"::OnE08");
deltimer(.@n$+"::OnE09");
deltimer(.@n$+"::OnE10");
deltimer(.@n$+"::OnE11");
deltimer(.@n$+"::OnE12");
killmonsterall(getmap());
}
// Died or logged out on Player Story 6 - Forgotten Shrine
if (compare(.@mapa$, "brb3")) {
//.@n$=instance_npcname("#Core02331");
.@q=getq(LoFQuest_Barbara);
// Reset quest to: Forgotten Chamber Puzzle incomplete
if (.@q < 4 && .@q >= 2) {
setq1 LoFQuest_Barbara, 1;
setq3 LoFQuest_Barbara, 1;
// FIXME: Enable Mana Stone#01863
dispbottom col(l("WARNING: You died at Forgotten Shrine and the Shrine defense triggered."), 1);
dispbottom col(l("WARNING: Your progress on the quest was lost!"), 1);
}
}
// Died or logged out during Sagratha Fight
if (compare(.@mapa$, "sgt2")) {
setq1 HurnscaldQuest_Sagratha, 3;
setq3 HurnscaldQuest_Sagratha, 0;
}
// First death produces a warning message
if (PC_DIE_COUNTER <= 1 && .@dead) {
dispbottom l("Dying outside a town square will cause EXP loss.");
}
// If you were travelling and died/logged out, cleaning is needed
if (@timer_navio_running) {
@timer_navio_running=0;
// Logged out? Correct your position to inside the ship
// Of course, this is messy... But still better than Save Point
if (!.@dead) {
if (compare(.@mapa$, "016-"))
warp "016-1", 28, 27;
else if (compare(.@mapa$, "002-"))
warp "002-1", 55, 40;
else
warp "Save", 0, 0;
}
}
// Crazyfefe hot fix
if (.@dead) {
// It was PK
if (killerrid > 2000000 && killerrid < 2100000) {
// PVP flag was off
/*
if (!getmapflag(.@mapa$, mf_pvp) && !getmapflag(.@mapa$, mf_pvp_noparty) && !getmapflag(.@mapa$, mf_pvpnoguild)) {
recovery(getcharid(3));
warp .@mapa$, .@a, .@b;
percentheal 100, 100;
dispbottom l("REVENGE TIME!");
.@trueid=getcharid(3);
//detachrid();
attachrid(killerrid);
setpcblock(PCBLOCK_ATTACK|PCBLOCK_SKILL|PCBLOCK_USEITEM|PCBLOCK_COMMANDS, true);
sc_start SC_WALKSPEED,120000,50;
sc_end SC_CASH_PLUSEXP;
sc_end SC_OVERLAPEXPUP;
sc_start SC_OVERLAPEXPUP, 300000, -20;
dispbottom l("For cowardingly killing in a \"secure\" area, you will be severely punished.");
//Karma+=1;
sc_start SC_STUN, 15000, 1, 10000, SCFLAG_NOAVOID|SCFLAG_FIXEDTICK;
addtimer(15000, "#mobptsys::OnUnlock");
percentheal -88, -100;
detachrid();
attachrid(.@trueid);
}
*/
HONOR+=1;
}
}
// This allows code to override death penalty, just once:
// @deathpenalty_override
// Valid values: 1- No penalty. 2- Halved penalty.
// You must also set: @deathpenalty_realvalue and @deathpenalty_realvaljob
if (@deathpenalty_override && .@dead) {
if (is_staff())
debugmes("Old values: %d %d Current Values: %d %d", @deathpenalty_realvalue, @deathpenalty_realvaljob, readparam(BaseExp), readparam(JobExp));
addtimer(300, "#QuirinoHUB::OnNoPenaltyCommand");
}
// Register logout time
if (!.@dead)
CHAREG_CLEANUP=gettimetick(2);
return;
}
// HUB_SkillInvoke ( )
function script HUB_SkillInvoke {
debugmes "Cast skill %d", @skillId;
// TODO: Detect what was script-cast and what was player-case. Then, readd RB_IRCBROADCAST
// If you can't do this: You can't do this
if (getskilllv(@skillId) < @skillLv)
Exception("System ERROR, HSI."+@skillId+" INVALID CAST (got "+@skillLv+" expected "+getskilllv(@skillId)+", sub-LC."+(getcharid(3)-2000000)+")", RB_DEBUGMES|RB_ISFATAL);
// Record to database
skillInvoke[@skillId] = skillInvoke[@skillId] + 1;
// Script-based skills
switch (@skillId) {
case TMW2_FAKESKILL:
atcommand("@refresh");
break;
case TMW2_FAKESKILL2:
CMD_toevent();
break;
case TMW2_CRAFT:
UserCtrlPanel();
break;
case TMW2_MPREGEN:
SK_mpregen();
break;
case EVOL_AREA_PROVOKE:
if (@skillTargetX && @skillTargetY)
massprovoke(1+@skillLv, getmap(), @skillTargetX, @skillTargetY);
else
massprovoke(1+@skillLv);
GetManaExp(@skillId, rand2(1,3));
break;
case TMW2_GD_INCALL:
GD_allboost();
GetManaExp(GD_DEVELOPMENT, 10);
break;
case TMW2_GD_REGEN:
GD_regenerating();
GetManaExp(GD_DEVELOPMENT, 10);
break;
case TMW2_GD_DEFUP:
GD_defboost();
GetManaExp(GD_DEVELOPMENT, 10);
break;
case TMW2_GD_BATTLEPLAN:
GD_atkboost();
GetManaExp(GD_DEVELOPMENT, 10);
break;
case TMW2_GD_ATKUP:
GD_atkboost2();
GetManaExp(GD_DEVELOPMENT, 10);
break;
case TMW2_GD_CRITUP:
GD_critboost();
GetManaExp(GD_DEVELOPMENT, 10);
break;
case TMW2_GD_AUTOREVIVE:
GD_autorevive();
GetManaExp(GD_DEVELOPMENT, 10);
break;
case TMW2_GDP_MAXPOWER:
SK_maximizepower();
GetManaExp(GD_DEVELOPMENT, 10);
break;
case TMW2_GDP_SPREGEN:
SK_spregen();
GetManaExp(GD_DEVELOPMENT, 10);
break;
// FIXME: Weapon Overload attack
case TMW2_OVERLOAD:
.@PW=200+(@skillLv > 3 ? @skillLv : 0)+(@skillLv > 7 ? @skillLv*2 : 0);
areaharm(@skillTarget, 0, AdjustAttackpower(.@PW), HARM_MISC);
break;
// Magic v3
case TMW2_NILFHEIM:
// Nilfheim cast on self?
.@PW=80+(10*@skillLv);
areaharm(getcharid(3), 8, AdjustSpellpower(.@PW), HARM_MAGI, Ele_Water);
areasc(8, 15000, SC_FREEZE, BL_PC | BL_MOB | BL_MER | BL_HOM, "filter_hostile");
// Maybe filter_notme() would work better, indeed
break;
case TMW2_JUDGMENT:
.@dmg=AdjustSpellpower(300);
areaharm(@skillTarget, 8, .@dmg, HARM_MAGI, Ele_Holy);
areasc2(getmap(), @skillTargetX, @skillTargetY, 2, 10000, SC_BLOODING, BL_MOB|BL_PC);
break;
/*
case TMW2_MANABOMB:
// TODO: areaharm(1x1 or same square?)
// This skill takes 100% mana for a 1:1 ratio damage
// And is a trick. Each level improves ratio in 1
// Has no cooldown, so it is powerful with pots
// And is a good starter offensive skill
areaharm(@skillTarget, 0, Sp*@skillLv, HARM_MAGI, Ele_Ghost);
Sp=0;
break;
// TODO: Ultimate Skills (T5/0)
// Support magic
// TODO: Debuffs
// TODO: Buffs
case TMW2_FIRSTAID:
.@PW=90+(10*@skillLv);
// First aid only works on you, so
.@heal=max(AdjustSpellpower(.@PW), AdjustAttackpower(.@PW));
heal .@heal, 0;
break;
case TMW2_HEALING:
.@PW=130+(20*@skillLv);
harm(@skillTarget, -AdjustSpellpower(.@PW), HARM_MISC);
break;
case TMW2_MAGNUSHEAL:
// Area healing
.@PW=200+(20*@skillLv);
areaharm(@skillTarget, 4, -AdjustSpellpower(.@PW), HARM_MISC, "filter_friendly");
break;
// Provoke: builtin, Mass Provoke: See above
// Mana Wisdom: Passive, Accumulate Power: builtin
// FIXME: Windwalker (deprecate?), Last Standing Man (2 MSP?)
// TODO: Ressurection skill
// Destructive Magic
case TMW2_FIREARROW:
.@PW=140+(10*@skillLv);
harm(@skillTarget, AdjustSpellpower(.@PW), HARM_MAGI, Ele_Fire);
break;
case TMW2_FIREBALL:
.@PW=140+(10*@skillLv);
areaharm(@skillTarget, 3, AdjustSpellpower(.@PW), HARM_MAGI, Ele_Fire);
break;
case TMW2_ARMAGEDDON:
.@PW=140+(10*@skillLv);
areasc2(getmap(), @skillTargetX, @skillTargetY, 2, 10000, SC_BLOODING, BL_MOB|BL_PC);
areaharm(@skillTarget, 8, AdjustSpellpower(.@PW), HARM_MAGI, Ele_Fire);
break;
case TMW2_FROSTDIVER:
.@PW=80+(10*@skillLv);
// 8% chance, 10s
sc_start SC_FREEZE, 10000, 1, 800, flag?, @skillTarget;
harm(@skillTarget, AdjustSpellpower(.@PW), HARM_MAGI, Ele_Water);
break;
case TMW2_FROSTNOVA:
.@PW=80+(10*@skillLv);
// TODO: All this in area
// 8% chance, 10s
sc_start SC_FREEZE, 10000, 1, 800, flag?, @skillTarget;
areaharm(@skillTarget, 6, AdjustSpellpower(.@PW), HARM_MAGI, Ele_Water);
break;
case TMW2_NILFHEIM:
// Already Implemented, Needs Cooldown/etc
break;
case TMW2_MAGICSTRIKE:
case TMW2_LIGHTNINGBOLT:
case TMW2_TEMPEST:
break;
case TMW2_HOLYLIGHT:
case TMW2_NAPALMBEAT:
case TMW2_JUDGEMENT:
break;
case TMW2_METEORSTRIKE:
case TMW2_METEORSHOWER:
case TMW2_GAIABREAK: // Defensive spell + single target earth DPS
break;
// Firewalk: Handled externally
// Physical Class - mostly builtin
// TODO: Archery effect-absorb skill
// TODO: Something powerful for swords?
// Brawling Class
case TMW2_BRAWLING:
// 75x3 = 225
.@PW=70+(5*@skillLv);
// Using a shield, so power is halved
if (getequipid(EQI_HAND_L) > 0)
.@PW=.@PW/2;
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
break;
case TMW2_BEARSTRIKE:
// 60x5 = 300
.@PW=55+(5*@skillLv);
// Using a shield, so power is halved
if (getequipid(EQI_HAND_L) > 0)
.@PW=.@PW/2;
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
break;
case TMW2_ALLINONE:
// 45x8 = 360
.@PW=40+(5*@skillLv);
// Using a shield, so power is halved
if (getequipid(EQI_HAND_L) > 0)
.@PW=.@PW/2;
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Fire);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Water);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Earth);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Wind);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Holy);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Shadow);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Ghost);
break;
*/
// CLASS_OTHER
case TMW2_PARUM:
SK_parum();
break;
case TMW2_DEMURE:
SK_Demure();
break;
case TMW2_DRAGOKIN:
SK_Dragokin();
break;
// Summons which never fail
case TMW2_ZARKOR:
SummonMagic(@skillId, CaveMaggot, 2, @skillLv);
GetManaExp(@skillId, 1);
break;
// Summons which may fail
case TMW2_KALMURK:
// TODO: Allow Giant Maggot summoning
SK_summon(Maggot, 2, any(1,2));
break;
case TMW2_KALWULF:
SK_summon(Wolvern, 4, any(3,4));
break;
case TMW2_KALBOO:
SK_summon(Mouboo, 4, any(2,3));
break;
case TMW2_KALSPIKE:
SK_summon(PoisonSpikyMushroom, 4, any(2,3));
break;
case TMW2_CUTEHEART:
SK_summon(Fluffy, 4, any(2,3));
break;
// Slightly more complex summons
case TMW2_LIMERIZER:
SK_summon(any(GreenSlime,AzulSlime,RedSlime,AngryYellowSlime), 2, any(3,4));
break;
case TMW2_FAIRYKINGDOM:
// TODO: Allow Pixie summoning
SK_summon(any(FireFairy, EarthFairy, WaterFairy, WindFairy, PoisonFairy), 4, any(3,4));
break;
// More complex summons
case TMW2_HALHISS:
.@mobId=Snake;
if (abizit() > 3 &&
GHMEMO[GHQ_GetQuestIDByMonsterID(MountainSnake)] >= 10000 &&
rand2(1,3) == 2)
{
.@mobId=MountainSnake;
}
SK_summon(.@mobId, 4, any(3,4));
break;
case TMW2_FROZENHEART:
.@mobId=Moggun;
if (rand2(6,12) < (abizit()*2)+1)
{
.@mobId=Yeti;
}
SK_summon(.@mobId, 4, any(3,4));
break;
case TMW2_STONEHEART:
.@mobId=Terranite;
if (rand2(9,12) < (abizit()*2)+1 &&
BaseLevel > 80)
{
.@mobId=TerraniteProtector;
}
SK_summon(.@mobId, 4, any(4,5));
break;
case TMW2_DUCKY:
if (alignment() < 0 && !isequippedcnt(AegisShield))
break;
SummonMagic(@skillId, Duck, 2, @skillLv);
GetManaExp(@skillId, 1);
break;
// Special exception
case TMW2_TRANSMIGRATION:
doevent("sk#mkpot::OnCall");
break;
// Experience only
case SM_BASH:
case MC_MAMMONITE:
case AC_SHOWER:
case KN_AUTOCOUNTER:
case ASC_METEORASSAULT:
case SN_SHARPSHOOTING:
case AL_HEAL:
case HW_MAGICPOWER:
case SM_PROVOKE:
case AB_HIGHNESSHEAL:
case SN_WINDWALK:
case MG_FIREBALL:
case AL_HOLYLIGHT:
case MG_SOULSTRIKE:
case MG_NAPALMBEAT:
case SO_FIREWALK:
case WZ_FROSTNOVA:
case MG_LIGHTNINGBOLT:
case TF_BACKSLIDING:
case MG_FIREWALL:
case ALL_FULL_THROTTLE:
case GC_DARKILLUSION:
case NV_TRICKDEAD:
case AC_CHARGEARROW:
GetManaExp(@skillId, rand2(1,3));
break;
}
// Debug
if ($@GM_OVERRIDE || debug)
debugmes "Cast skill %d on level %d", @skillId, @skillLv;
return;
}
// When you kill a player, some special care is needed
// Only a few maps will give you experience for PK: Tulimshar's Guards Arena,
// Frostia Imperial PVP Arena, Call Of Dusty, Arena Quirino Voraz.
// HUB_PvP ( ) - killedrid must be set
function script HUB_PvP {
// Update global PK count
$PLAYERS_KILLED+=1;
// Prepare local variables
.@atk=get_BR(getcharid(3));
.@def=get_BR(killedrid);
.@honor=calc_HR(getcharid(3), killedrid);
.@bxp=max(readparam(BaseLevel, killedrid), .@def);
.@jxp=readparam(JobLevel, killedrid);
.@m$=getmap();
.@gm=getgroupid(killedrid);
// This is an official PVP Map
if (ispvpmap(.@m$)) {
// Honorable Death
if (.@honor >= 0) {
HONOR+=.@honor;
} else {
// Dishonorable... But... Legit?
if (is_bandit(killedrid))
HONOR+=1;
else
HONOR+=.@honor; // It's negative.
}
// PvP, and a GM was slain
if (.@gm >= 80) {
rentitem MurdererCrown, (86400*7); // 1 week
kamibroadcast("%s killed Game Master \"%s\", and now may wear a %s for a week.", strcharinfo(0), strcharinfo(0, "", killedrid), getitemlink(MurdererCrown));
}
// It was a duel!
} else {
// Honorable Duel: HONOR +30%
if (.@honor > 0)
HONOR+=max(1, .@honor*3/10);
else if (.@honor < 0)
HONOR+=1;
// ^ Dishonorable duel, but was a duel!
}
// Report about honor
dispbottom l("%d vs %d: Honor (%d)", .@atk, .@def, .@honor);
// TODO: Start using readparam2() to read if the opponent was worthy
// That is, read total attack, defense, HP, evasion and hit chance
// And compare with your own readparam2(), then use a % and a table
// based on your (assassin's) level.
if (compare(.@m$, "001-8")) {
// Quirino Voraz PVP Arena
// You get 5 times killed player level, and 1 time job level
getexp .@bxp*5, .@jxp;
} else if (compare(.@m$, "ARENA") || compare(.@m$, "003-13")) {
// Tulimshar Duel Arena
// You get 3 times killed player level, and 2 times job level
getexp .@bxp*3, .@jxp*2;
} else if (compare(.@m$, "001-10")) {
// Call Of Dusty
// You get 3 times killed player level, and 3 times job level
getexp .@bxp*3, .@jxp*3;
} else if (compare(.@m$, "001-10-1")) {
// Call Of Dusty Boss Room
// You _may_ get a Bottled Dusty at random, but dead player status affect
if (.@def > .@atk/10) {
if (rand2(0,250) < readparam2(bLuk)+readparam2(bLuk, killedrid))
getitem BottledDust, any(1,1,2);
}
} else {
// Anywhere else
// You get 0.5 times killed player level, and 0 times job level
getexp (.@bxp/2), 0;
}
return;
}