Merge pull request #66 from MindRapist/mr-8

MR-8: Nemere Dungeon
This commit is contained in:
rtw1x1
2026-01-21 18:37:03 +00:00
committed by GitHub
24 changed files with 1317 additions and 90 deletions

View File

@@ -306,13 +306,13 @@ void CClientManager::QUERY_PLAYER_LOAD(CPeer * peer, DWORD dwHandle, TPlayerLoad
"SELECT dwPID,szName,szState,lValue FROM quest%s WHERE dwPID=%d AND lValue<>0",
GetTablePostfix(), pTab->id);
CDBManager::instance().ReturnQuery(szQuery, QID_QUEST, peer->GetHandle(), new ClientHandleInfo(dwHandle,0,packet->account_id));
CDBManager::instance().ReturnQuery(szQuery, QID_QUEST, peer->GetHandle(), new ClientHandleInfo(dwHandle, 0, packet->account_id));
// Affect
snprintf(szQuery, sizeof(szQuery),
"SELECT dwPID,bType,bApplyOn,lApplyValue,dwFlag,lDuration,lSPCost FROM affect%s WHERE dwPID=%d",
GetTablePostfix(), pTab->id);
CDBManager::instance().ReturnQuery(szQuery, QID_AFFECT, peer->GetHandle(), new ClientHandleInfo(dwHandle));
CDBManager::instance().ReturnQuery(szQuery, QID_AFFECT, peer->GetHandle(), new ClientHandleInfo(dwHandle, packet->player_id));
}
/////////////////////////////////////////////
// 2) 아이템이 DBCache 에 없음 : DB 에서 가져옴
@@ -601,6 +601,22 @@ void CClientManager::RESULT_COMPOSITE_PLAYER(CPeer * peer, SQLMsg * pMsg, DWORD
case QID_AFFECT:
sys_log(0, "QID_AFFECT %u", info->dwHandle);
// MR-8: Fix "when_login" being loaded before character affects
if (!mysql_num_rows(pSQLResult))
{
TPacketAffectElement pAffElem{};
DWORD dwCount = 0;
peer->EncodeHeader(HEADER_DG_AFFECT_LOAD, info->dwHandle, sizeof(DWORD) + sizeof(DWORD) + sizeof(TPacketAffectElement) * dwCount);
peer->Encode(&info->player_id, sizeof(DWORD));
peer->Encode(&dwCount, sizeof(DWORD));
peer->Encode(&pAffElem, sizeof(TPacketAffectElement) * dwCount);
break;
}
// MR-8: -- END OF -- Fix "when_login" being loaded before character affects
RESULT_AFFECT_LOAD(peer, pSQLResult, info->dwHandle);
break;
/*

View File

@@ -272,7 +272,9 @@ struct FSkillEarthQuake
ch->Damage( pAttacker, dam, DAMAGE_TYPE_ICE);
SkillAttackAffect( ch, 1000, IMMUNE_STUN, AFFECT_STUN, POINT_NONE, 0, AFF_STUN, sec, "BDRAGON_STUN" );
// MR-8: Snow dungeon - All-damage immunity with exceptions
SkillAttackAffect( pAttacker, ch, 1000, IMMUNE_STUN, AFFECT_STUN, POINT_NONE, 0, AFF_STUN, sec, "BDRAGON_STUN" );
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
sys_log(0, "BlueDragon: EarthQuake to %s addPct(%d) dam(%d) sec(%d)", ch->GetName(), addPct, dam, sec);

View File

@@ -40,6 +40,16 @@ inline void AttackAffect(LPCHARACTER pkAttacker,
int time,
const char* name)
{
// MR-8: Snow dungeon - All-damage immunity with exceptions
if (pkVictim->IsDamageImmune() && (pkVictim->IsMonster() || pkVictim->IsStone() || pkVictim->IsDoor()))
{
if (!pkVictim->CheckDamageImmunityConditions(pkAttacker))
{
return; // Immunity prevents stun application
}
}
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
if (pkAttacker->GetPoint(att_point) && !pkVictim->IsAffectFlag(affect_flag))
{
if (number(1, 100) <= pkAttacker->GetPoint(att_point) && !pkVictim->IsImmune(immune_flag))
@@ -58,7 +68,9 @@ inline void AttackAffect(LPCHARACTER pkAttacker,
}
}
inline void SkillAttackAffect(LPCHARACTER pkVictim,
// MR-8: Snow dungeon - All-damage immunity with exceptions
inline void SkillAttackAffect(LPCHARACTER pkAttacker,
LPCHARACTER pkVictim,
int success_pct,
DWORD immune_flag,
DWORD affect_idx,
@@ -68,6 +80,15 @@ inline void SkillAttackAffect(LPCHARACTER pkVictim,
int time,
const char* name)
{
if (pkVictim->IsDamageImmune() && (pkVictim->IsMonster() || pkVictim->IsStone() || pkVictim->IsDoor()))
{
if (!pkVictim->CheckDamageImmunityConditions(pkAttacker))
{
return; // Immunity prevents skill affect application
}
}
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
if (success_pct && !pkVictim->IsAffectFlag(affect_flag))
{
if (number(1, 1000) <= success_pct && !pkVictim->IsImmune(immune_flag))

View File

@@ -376,6 +376,11 @@ void CHARACTER::Initialize()
memset(&m_tvLastSyncTime, 0, sizeof(m_tvLastSyncTime));
m_iSyncHackCount = 0;
// MR-8: Snow dungeon - All-damage immunity with exceptions
m_bDamageImmune = false;
m_vecDamageImmunityConditions.clear();
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
}
void CHARACTER::Create(const char * c_pszName, DWORD vid, bool isPC)
@@ -1502,6 +1507,17 @@ bool CHARACTER::Show(long lMapIndex, long x, long y, long z, bool bShowSpawnMoti
REMOVE_BIT(m_bAddChrState, ADD_CHARACTER_STATE_SPAWN);
SetValidComboInterval(0);
// MR-8: Resync affects when moving within same map (fixes dungeon relog affect icon bug)
if (IsPC() && GetDesc() != NULL) {
// Re-send all affect packets to client to ensure icons are synchronized
itertype(m_list_pkAffect) it = m_list_pkAffect.begin();
while (it != m_list_pkAffect.end())
SendAffectAddPacket(GetDesc(), *it++);
}
// MR-8: -- END OF -- Resync affects when moving within same map
return true;
}
@@ -7400,5 +7416,184 @@ int CHARACTER::GetSkillPowerByLevel(int level, bool bMob) const
void CHARACTER::SetLastPMPulse(void)
{
m_iLastPMPulse = thecore_pulse() + passes_per_sec;
m_iLastPMPulse = thecore_pulse() + passes_per_sec;
}
// MR-8: Snow dungeon - All-damage immunity with exceptions
void CHARACTER::SetDamageImmunity(bool bImmune)
{
m_bDamageImmune = bImmune;
if (test_server)
{
DWORD vid = GetVID();
sys_log(0, "SetDamageImmunity: %s [%u] immune=%d conditions=%d",
GetName(), vid, bImmune, m_vecDamageImmunityConditions.size());
}
}
void CHARACTER::AddDamageImmunityCondition(BYTE bType, DWORD dwValue, const std::string& strExtra)
{
SDamageImmunityCondition cond(bType, dwValue, strExtra);
m_vecDamageImmunityConditions.push_back(cond);
if (test_server)
{
DWORD vid = GetVID();
sys_log(0, "AddDamageImmunityCondition: %s [%u] type=%d value=%u extra=%s",
GetName(), vid, bType, dwValue, strExtra.c_str());
}
}
void CHARACTER::ClearDamageImmunityConditions()
{
m_vecDamageImmunityConditions.clear();
if (test_server)
{
DWORD vid = GetVID();
sys_log(0, "ClearDamageImmunityConditions: %s [%u]", GetName(), vid);
}
}
bool CHARACTER::CheckDamageImmunityConditions(LPCHARACTER pAttacker) const
{
if (!pAttacker)
return false;
// If no conditions are set, block all damage
if (m_vecDamageImmunityConditions.empty())
{
if (test_server && m_bDamageImmune)
{
DWORD vid = GetVID();
sys_err("CheckDamageImmunityConditions: %s [%u] has immunity flag but NO conditions - blocking damage",
GetName(), vid);
}
return false;
}
std::vector<BYTE> allowedJobs;
// All conditions must pass (AND logic)
for (std::vector<SDamageImmunityCondition>::const_iterator it = m_vecDamageImmunityConditions.begin();
it != m_vecDamageImmunityConditions.end(); ++it)
{
const SDamageImmunityCondition& cond = *it;
switch (cond.bType)
{
case DAMAGE_IMMUNITY_COND_AFFECT:
if (!pAttacker->FindAffect(cond.dwValue))
{
if (test_server)
pAttacker->ChatPacket(CHAT_TYPE_INFO, "Target immune: need affect %u", cond.dwValue);
return false;
}
break;
case DAMAGE_IMMUNITY_COND_LEVEL_MIN:
if (pAttacker->GetLevel() < (int)cond.dwValue)
{
if (test_server)
pAttacker->ChatPacket(CHAT_TYPE_INFO, "Target immune: need level >= %u", cond.dwValue);
return false;
}
break;
case DAMAGE_IMMUNITY_COND_LEVEL_MAX:
if (pAttacker->GetLevel() > (int)cond.dwValue)
{
if (test_server)
pAttacker->ChatPacket(CHAT_TYPE_INFO, "Target immune: need level <= %u", cond.dwValue);
return false;
}
break;
case DAMAGE_IMMUNITY_COND_QUEST_FLAG:
if (!pAttacker->IsPC())
return false;
if (pAttacker->GetQuestFlag(cond.strExtra) != (int)cond.dwValue)
{
if (test_server)
pAttacker->ChatPacket(CHAT_TYPE_INFO, "Target immune: need quest flag %s == %u",
cond.strExtra.c_str(), cond.dwValue);
return false;
}
break;
case DAMAGE_IMMUNITY_COND_ITEM_EQUIPPED:
if (!pAttacker->IsEquipUniqueItem(cond.dwValue))
{
if (test_server)
pAttacker->ChatPacket(CHAT_TYPE_INFO, "Target immune: need item %u equipped", cond.dwValue);
return false;
}
break;
case DAMAGE_IMMUNITY_COND_EMPIRE:
if (pAttacker->GetEmpire() != (BYTE)cond.dwValue)
{
if (test_server)
pAttacker->ChatPacket(CHAT_TYPE_INFO, "Target immune: need empire %u", cond.dwValue);
return false;
}
break;
case DAMAGE_IMMUNITY_COND_JOB:
if (!pAttacker->IsPC())
return false;
if ((BYTE)cond.dwValue >= JOB_MAX_NUM)
{
if (test_server)
pAttacker->ChatPacket(CHAT_TYPE_INFO, "Target immune: invalid job value");
return false;
}
allowedJobs.push_back((BYTE)cond.dwValue);
break;
default:
sys_err("Unknown damage immunity condition type: %d", cond.bType);
return false;
}
}
// Check job requirements (OR logic among jobs, AND with other conditions)
if (!allowedJobs.empty())
{
bool bJobAllowed = false;
BYTE bJob = pAttacker->GetJob();
for (std::vector<BYTE>::const_iterator jt = allowedJobs.begin(); jt != allowedJobs.end(); ++jt)
{
if (bJob == *jt)
{
bJobAllowed = true;
break;
}
}
if (!bJobAllowed)
{
if (test_server)
pAttacker->ChatPacket(CHAT_TYPE_INFO, "Target immune: need job match (your job: %d)", bJob);
return false;
}
}
// All conditions passed
return true;
}
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions

View File

@@ -1641,6 +1641,42 @@ class CHARACTER : public CEntity, public CFSM, public CHorseRider
void ApplyMobAttribute(const TMobTable* table);
// End of Resists & Proofs
////////////////////////////////////////////////////////////////////////////////////////
// MR-8: Snow dungeon - All-damage immunity with exceptions
public:
enum EDamageImmunityConditionType
{
DAMAGE_IMMUNITY_COND_AFFECT = 0, // Has affect
DAMAGE_IMMUNITY_COND_LEVEL_MIN = 1, // Level >= value
DAMAGE_IMMUNITY_COND_LEVEL_MAX = 2, // Level <= value
DAMAGE_IMMUNITY_COND_QUEST_FLAG = 3, // Quest flag == value
DAMAGE_IMMUNITY_COND_ITEM_EQUIPPED = 4, // Has item vnum equipped
DAMAGE_IMMUNITY_COND_EMPIRE = 5, // Is specific empire
DAMAGE_IMMUNITY_COND_JOB = 6, // Is specific job
};
struct SDamageImmunityCondition
{
BYTE bType;
DWORD dwValue;
std::string strExtra; // For quest flag names, etc.
SDamageImmunityCondition() : bType(0), dwValue(0) {}
SDamageImmunityCondition(BYTE t, DWORD v, const std::string& e = "")
: bType(t), dwValue(v), strExtra(e) {}
};
void SetDamageImmunity(bool bImmune);
bool IsDamageImmune() const { return m_bDamageImmune; }
void AddDamageImmunityCondition(BYTE bType, DWORD dwValue, const std::string& strExtra = "");
void ClearDamageImmunityConditions();
bool CheckDamageImmunityConditions(LPCHARACTER pAttacker) const;
protected:
bool m_bDamageImmune;
std::vector<SDamageImmunityCondition> m_vecDamageImmunityConditions;
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
////////////////////////////////////////////////////////////////////////////////////////
// QUEST
//

View File

@@ -1769,6 +1769,39 @@ bool CHARACTER::Damage(LPCHARACTER pAttacker, int dam, EDamageType type) // retu
//PROF_UNIT puAttr("Attr");
// MR-8: Snow dungeon - All-damage immunity with exceptions
// Damage Immunity System - per-hit check (O(1) operation)
// Check immunity for monsters/stones/doors with the flag set
if (m_bDamageImmune && (IsMonster() || IsStone() || IsDoor()))
{
// Check if attacker meets all required conditions
// NOTE: If flag is set but conditions are empty, this will return false (block all damage)
if (!CheckDamageImmunityConditions(pAttacker))
{
// Attacker doesn't meet conditions - send MISS packet directly
if (pAttacker->IsPC())
{
TPacketGCDamageInfo damageInfo;
memset(&damageInfo, 0, sizeof(TPacketGCDamageInfo));
damageInfo.header = HEADER_GC_DAMAGE_INFO;
damageInfo.dwVID = (DWORD)GetVID();
damageInfo.flag = DAMAGE_DODGE;
damageInfo.damage = 0;
if (pAttacker->GetDesc() != NULL)
{
pAttacker->GetDesc()->Packet(&damageInfo, sizeof(TPacketGCDamageInfo));
}
}
return false;
}
// All conditions met - allow damage to pass through
}
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
//
// 마법형 스킬과, 레인지형 스킬은(궁자객) 크리티컬과, 관통공격 계산을 한다.
// 원래는 하지 않아야 하는데 Nerf(다운밸런스)패치를 할 수 없어서 크리티컬과

View File

@@ -42,6 +42,15 @@ bool CHARACTER::StartRiding()
return false;
}
// MR-8: Prevent mounting in Nemere's Watchtower
long lMapIndex = GetMapIndex();
if (lMapIndex >= 352 * 10000 && lMapIndex < 353 * 10000)
{
ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You cannot ride your horse in Nemere's Watchtower."));
return false;
}
// MR-8: -- END OF -- Prevent mounting in Nemere's Watchtower
DWORD dwMountVnum = m_chHorse ? m_chHorse->GetRaceNum() : GetMyHorseVnum();
@@ -151,6 +160,16 @@ void CHARACTER::HorseSummon(bool bSummon, bool bFromFar, DWORD dwVnum, const cha
if (IsRiding())
return;
// MR-8: Prevent mounting in Nemere's Watchtower
long lMapIndex = GetMapIndex();
if (lMapIndex >= 352 * 10000 && lMapIndex < 353 * 10000)
{
ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You cannot ride your horse in Nemere's Watchtower."));
return;
}
// MR-8: -- END OF -- Prevent mounting in Nemere's Watchtower
sys_log(0, "HorseSummon : %s lv:%d bSummon:%d fromFar:%d", GetName(), GetLevel(), bSummon, bFromFar);
long x = GetX();
@@ -357,10 +376,12 @@ bool CHARACTER::CanUseHorseSkill()
{
if(IsRiding())
{
if (GetHorseGrade() == 3)
// MR-8: CanUseHorseSkill() grade fix
if (GetHorseGrade() >= 3)
return true;
else
return false;
// MR-8: -- END OF -- CanUseHorseSkill() grade fix
if(GetMountVnum())
{

View File

@@ -7601,6 +7601,19 @@ bool CHARACTER::CanEquipNow(const LPITEM item, const TItemPos& srcCell, const TI
return false;
}
// MR-8: Prevent mounting in Nemere's Watchtower
if (item->GetSpecialGroup() == UNIQUE_GROUP_SPECIAL_RIDE)
{
long lMapIndex = GetMapIndex();
bool isInNemereDungeon = lMapIndex >= 352 * 10000 && lMapIndex < 353 * 10000;
if (isInNemereDungeon && !IsRiding())
{
ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You cannot ride your horse in Nemere's Watchtower."));
return false;
}
}
// MR-8: -- END OF -- Prevent mounting in Nemere's Watchtower
}
return true;

View File

@@ -558,7 +558,82 @@ bool CHARACTER_MANAGER::SpawnGroupGroup(DWORD dwVnum, long lMapIndex, int sx, in
}
}
LPCHARACTER CHARACTER_MANAGER::SpawnGroup(DWORD dwVnum, long lMapIndex, int sx, int sy, int ex, int ey, LPREGEN pkRegen, bool bAggressive_, LPDUNGEON pDungeon)
// MR-8: Snow dungeon - All-damage immunity with exceptions
LPCHARACTER CHARACTER_MANAGER::SpawnGroupWithVIDs(DWORD dwVnum, long lMapIndex, int sx, int sy, int ex, int ey, LPREGEN pkRegen, bool bAggressive_, LPDUNGEON pDungeon, std::vector<DWORD>& rVids)
{
CMobGroup * pkGroup = CMobManager::Instance().GetGroup(dwVnum);
if (!pkGroup)
{
sys_err("NOT_EXIST_GROUP_VNUM(%u) Map(%u) ", dwVnum, lMapIndex);
return NULL;
}
LPCHARACTER pkChrMaster = NULL;
LPPARTY pkParty = NULL;
const std::vector<DWORD> & c_rdwMembers = pkGroup->GetMemberVector();
bool bSpawnedByStone = false;
bool bAggressive = bAggressive_;
if (m_pkChrSelectedStone)
{
bSpawnedByStone = true;
if (m_pkChrSelectedStone->GetDungeon())
bAggressive = true;
}
LPCHARACTER chLeader = NULL;
for (DWORD i = 0; i < c_rdwMembers.size(); ++i)
{
LPCHARACTER tch = SpawnMobRange(c_rdwMembers[i], lMapIndex, sx, sy, ex, ey, true, bSpawnedByStone);
if (!tch)
{
if (i == 0) // 못만든 몬스터가 대장일 경우에는 그냥 실패
return NULL;
continue;
}
if (i == 0)
chLeader = tch;
rVids.push_back(tch->GetVID());
tch->SetDungeon(pDungeon);
sx = tch->GetX() - number(300, 500);
sy = tch->GetY() - number(300, 500);
ex = tch->GetX() + number(300, 500);
ey = tch->GetY() + number(300, 500);
if (m_pkChrSelectedStone)
tch->SetStone(m_pkChrSelectedStone);
else if (pkParty)
{
pkParty->Join(tch->GetVID());
pkParty->Link(tch);
}
else if (!pkChrMaster)
{
pkChrMaster = tch;
pkChrMaster->SetRegen(pkRegen);
pkParty = CPartyManager::instance().CreateParty(pkChrMaster);
}
if (bAggressive)
tch->SetAggressive();
}
return chLeader;
}
LPCHARACTER CHARACTER_MANAGER::SpawnGroupWithImmunity(DWORD dwVnum, long lMapIndex, int sx, int sy, int ex, int ey, LPREGEN pkRegen, bool bAggressive_, LPDUNGEON pDungeon, const std::vector<CHARACTER::SDamageImmunityCondition>& conditions)
{
CMobGroup * pkGroup = CMobManager::Instance().GetGroup(dwVnum);
@@ -603,6 +678,14 @@ LPCHARACTER CHARACTER_MANAGER::SpawnGroup(DWORD dwVnum, long lMapIndex, int sx,
tch->SetDungeon(pDungeon);
// Apply damage immunity atomically before mob is attackable
if (!conditions.empty())
{
tch->SetDamageImmunity(true);
for (const auto& cond : conditions)
tch->AddDamageImmunityCondition(cond.bType, cond.dwValue, cond.strExtra);
}
sx = tch->GetX() - number(300, 500);
sy = tch->GetY() - number(300, 500);
ex = tch->GetX() + number(300, 500);
@@ -630,6 +713,13 @@ LPCHARACTER CHARACTER_MANAGER::SpawnGroup(DWORD dwVnum, long lMapIndex, int sx,
return chLeader;
}
LPCHARACTER CHARACTER_MANAGER::SpawnGroup(DWORD dwVnum, long lMapIndex, int sx, int sy, int ex, int ey, LPREGEN pkRegen, bool bAggressive_, LPDUNGEON pDungeon)
{
std::vector<DWORD> dummy;
return SpawnGroupWithVIDs(dwVnum, lMapIndex, sx, sy, ex, ey, pkRegen, bAggressive_, pDungeon, dummy);
}
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
struct FuncUpdateAndResetChatCounter
{
void operator () (LPCHARACTER ch)

View File

@@ -9,9 +9,10 @@
#include "common/length.h"
#include "vid.h"
#include "char.h"
class CDungeon;
class CHARACTER;
// class CHARACTER;
class CharacterVectorInteractor;
class CHARACTER_MANAGER : public singleton<CHARACTER_MANAGER>
@@ -40,6 +41,10 @@ class CHARACTER_MANAGER : public singleton<CHARACTER_MANAGER>
LPCHARACTER SpawnMob(DWORD dwVnum, long lMapIndex, long x, long y, long z, bool bSpawnMotion = false, int iRot = -1, bool bShow = true);
LPCHARACTER SpawnMobRange(DWORD dwVnum, long lMapIndex, int sx, int sy, int ex, int ey, bool bIsException=false, bool bSpawnMotion = false , bool bAggressive = false);
LPCHARACTER SpawnGroup(DWORD dwVnum, long lMapIndex, int sx, int sy, int ex, int ey, LPREGEN pkRegen = NULL, bool bAggressive_ = false, LPDUNGEON pDungeon = NULL);
// MR-8: Snow dungeon - All-damage immunity with exceptions
LPCHARACTER SpawnGroupWithVIDs(DWORD dwVnum, long lMapIndex, int sx, int sy, int ex, int ey, LPREGEN pkRegen, bool bAggressive_, LPDUNGEON pDungeon, std::vector<DWORD>& rVids);
LPCHARACTER SpawnGroupWithImmunity(DWORD dwVnum, long lMapIndex, int sx, int sy, int ex, int ey, LPREGEN pkRegen, bool bAggressive_, LPDUNGEON pDungeon, const std::vector<CHARACTER::SDamageImmunityCondition>& conditions);
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
bool SpawnGroupGroup(DWORD dwVnum, long lMapIndex, int sx, int sy, int ex, int ey, LPREGEN pkRegen = NULL, bool bAggressive_ = false, LPDUNGEON pDungeon = NULL);
bool SpawnMoveGroup(DWORD dwVnum, long lMapIndex, int sx, int sy, int ex, int ey, int tx, int ty, LPREGEN pkRegen = NULL, bool bAggressive_ = false);
LPCHARACTER SpawnMobRandomPosition(DWORD dwVnum, long lMapIndex);

View File

@@ -167,7 +167,17 @@ void CHARACTER::AttackedByFire(LPCHARACTER pkAttacker, int amount, int count)
if (m_pkFireEvent)
return;
AddAffect(AFFECT_FIRE, POINT_NONE, 0, AFF_FIRE, count*3+1, 0, true);
// MR-8: Check damage immunity system - prevent fire application if conditions not met
if (m_bDamageImmune && (IsMonster() || IsStone() || IsDoor()))
{
if (!CheckDamageImmunityConditions(pkAttacker))
{
return; // Immunity prevents fire application
}
}
AddAffect(AFFECT_FIRE, POINT_NONE, 0, AFF_FIRE, count * 3 + 1, 0, true);
// MR-8: -- END OF -- Check damage immunity system - prevent fire application if conditions not met
TFireEventInfo* info = AllocEventInfo<TFireEventInfo>();
@@ -187,6 +197,16 @@ void CHARACTER::AttackedByPoison(LPCHARACTER pkAttacker)
if (m_bHasPoisoned && !IsPC()) // 몬스터는 독이 한번만 걸린다.
return;
// MR-8: Check damage immunity system - prevent poison application if conditions not met
if (m_bDamageImmune && (IsMonster() || IsStone() || IsDoor()))
{
if (!CheckDamageImmunityConditions(pkAttacker))
{
return; // Immunity prevents poison application
}
}
// MR-8: -- END OF -- Check damage immunity system - prevent poison application if conditions not met
if (pkAttacker && pkAttacker->GetLevel() < GetLevel())
{
int delta = GetLevel() - pkAttacker->GetLevel();

View File

@@ -1441,13 +1441,14 @@ struct FuncSplashDamage
iDur += m_pkChr->GetPoint(POINT_PARTY_BUFFER_BONUS);
// MR-8: Snow dungeon - All-damage immunity with exceptions
if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_STUN))
{
SkillAttackAffect(pkChrVictim, iPct, IMMUNE_STUN, AFFECT_STUN, POINT_NONE, 0, AFF_STUN, iDur, m_pkSk->szName);
SkillAttackAffect(m_pkChr, pkChrVictim, iPct, IMMUNE_STUN, AFFECT_STUN, POINT_NONE, 0, AFF_STUN, iDur, m_pkSk->szName);
}
else if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_SLOW))
{
SkillAttackAffect(pkChrVictim, iPct, IMMUNE_SLOW, AFFECT_SLOW, POINT_MOV_SPEED, -30, AFF_SLOW, iDur, m_pkSk->szName);
SkillAttackAffect(m_pkChr, pkChrVictim, iPct, IMMUNE_SLOW, AFFECT_SLOW, POINT_MOV_SPEED, -30, AFF_SLOW, iDur, m_pkSk->szName);
}
else if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_FIRE_CONT))
{
@@ -1472,6 +1473,7 @@ struct FuncSplashDamage
if (number(1, 100) <= iPct)
pkChrVictim->AttackedByPoison(m_pkChr);
}
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
}
if (IS_SET(m_pkSk->dwFlag, SKILL_FLAG_CRUSH | SKILL_FLAG_CRUSH_LONG) &&
@@ -1510,11 +1512,13 @@ struct FuncSplashDamage
if (m_pkChr->IsPC() && m_pkChr->m_SkillUseInfo[m_pkSk->dwVnum].GetMainTargetVID() == (DWORD) pkChrVictim->GetVID())
{
// MR-8: Snow dungeon - All-damage immunity with exceptions
//if (!g_iUseLocale)
if (LC_IsYMIR())
SkillAttackAffect(pkChrVictim, 1000, IMMUNE_STUN, m_pkSk->dwVnum, POINT_NONE, 0, AFF_STUN, 3, m_pkSk->szName);
SkillAttackAffect(m_pkChr, pkChrVictim, 1000, IMMUNE_STUN, m_pkSk->dwVnum, POINT_NONE, 0, AFF_STUN, 3, m_pkSk->szName);
else
SkillAttackAffect(pkChrVictim, 1000, IMMUNE_STUN, m_pkSk->dwVnum, POINT_NONE, 0, AFF_STUN, 4, m_pkSk->szName);
SkillAttackAffect(m_pkChr, pkChrVictim, 1000, IMMUNE_STUN, m_pkSk->dwVnum, POINT_NONE, 0, AFF_STUN, 4, m_pkSk->szName);
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
}
else
{
@@ -1985,9 +1989,14 @@ int CHARACTER::ComputeSkill(DWORD dwVnum, LPCHARACTER pkVictim, BYTE bSkillLevel
CSkillProto* pkSk = CSkillManager::instance().Get(dwVnum);
const bool bCanUseHorseSkill = CanUseHorseSkill();
// 말을 타고있지만 스킬은 사용할 수 없는 상태라면 return
if (false == bCanUseHorseSkill && true == IsRiding())
return BATTLE_NONE;
// MR-8: Flame Ghost skill fix on mounting
if (dwVnum != SKILL_MUYEONG)
{
// 말을 타고있지만 스킬은 사용할 수 없는 상태라면 return
if (false == bCanUseHorseSkill && true == IsRiding())
return BATTLE_NONE;
}
// MR-8: -- END OF -- Flame Ghost skill fix on mounting
if (IsPolymorphed())
return BATTLE_NONE;
@@ -1998,12 +2007,16 @@ int CHARACTER::ComputeSkill(DWORD dwVnum, LPCHARACTER pkVictim, BYTE bSkillLevel
if (!pkSk)
return BATTLE_NONE;
if (bCanUseHorseSkill && pkSk->dwType != SKILL_TYPE_HORSE)
return BATTLE_NONE;
// MR-8: Flame Ghost skill fix on mounting
if (dwVnum != SKILL_MUYEONG)
{
if (bCanUseHorseSkill && pkSk->dwType != SKILL_TYPE_HORSE)
return BATTLE_NONE;
if (!bCanUseHorseSkill && pkSk->dwType == SKILL_TYPE_HORSE)
return BATTLE_NONE;
if (!bCanUseHorseSkill && pkSk->dwType == SKILL_TYPE_HORSE)
return BATTLE_NONE;
}
// MR-8: -- END OF -- Flame Ghost skill fix on mounting
// 상대방에게 쓰는 것이 아니면 나에게 써야 한다.
if (IS_SET(pkSk->dwFlag, SKILL_FLAG_SELFONLY))

View File

@@ -65,15 +65,17 @@ void Command_ApplyAffect(LPCHARACTER ch, const char* argument, const char* affec
return;
}
// MR-8: Snow dungeon - All-damage immunity with exceptions
switch (cmdAffect)
{
case COMMANDAFFECT_STUN:
SkillAttackAffect(tch, 1000, IMMUNE_STUN, AFFECT_STUN, POINT_NONE, 0, AFF_STUN, 30, "GM_STUN");
SkillAttackAffect(ch, tch, 1000, IMMUNE_STUN, AFFECT_STUN, POINT_NONE, 0, AFF_STUN, 30, "GM_STUN");
break;
case COMMANDAFFECT_SLOW:
SkillAttackAffect(tch, 1000, IMMUNE_SLOW, AFFECT_SLOW, POINT_MOV_SPEED, -30, AFF_SLOW, 30, "GM_SLOW");
SkillAttackAffect(ch, tch, 1000, IMMUNE_SLOW, AFFECT_SLOW, POINT_MOV_SPEED, -30, AFF_SLOW, 30, "GM_SLOW");
break;
}
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
sys_log(0, "%s %s", arg1, affectName);

View File

@@ -763,6 +763,46 @@ LPCHARACTER CDungeon::SpawnMob_ac_dir(DWORD vnum, int x, int y, int dir)
return ch;
}
// MR-8: Snow dungeon - All-damage immunity with exceptions
LPCHARACTER CDungeon::SpawnMobWithImmunity(DWORD vnum, int x, int y, int dir, const std::vector<CHARACTER::SDamageImmunityCondition>& conditions)
{
LPSECTREE_MAP pkSectreeMap = SECTREE_MANAGER::instance().GetMap(m_lMapIndex);
if (pkSectreeMap == NULL) {
sys_err("CDungeon: SECTREE_MAP not found for #%ld", m_lMapIndex);
return NULL;
}
long ax = pkSectreeMap->m_setting.iBaseX + x * 100;
long ay = pkSectreeMap->m_setting.iBaseY + y * 100;
int rot = (dir == 0 ? -1 : (dir - 1) * 45);
// Create but do NOT show yet
LPCHARACTER ch = CHARACTER_MANAGER::instance().SpawnMob(vnum, m_lMapIndex, ax, ay, 0, false, rot, false);
if (!ch)
return NULL;
// Apply damage immunity atomically before showing
if (!conditions.empty())
{
ch->SetDamageImmunity(true);
for (const auto& c : conditions)
ch->AddDamageImmunityCondition(c.bType, c.dwValue, c.strExtra);
}
// Link to dungeon and show
ch->SetDungeon(this);
if (!ch->Show(m_lMapIndex, ax, ay, 0, false))
{
M2_DESTROY_CHARACTER(ch);
sys_log(0, "SpawnMobWithImmunity: cannot show monster");
return NULL;
}
return ch;
}
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
void CDungeon::SpawnNameMob(DWORD vnum, int x, int y, const char* name)
{
LPSECTREE_MAP pkSectreeMap = SECTREE_MANAGER::instance().GetMap(m_lMapIndex);
@@ -833,6 +873,67 @@ LPCHARACTER CDungeon::SpawnGroup(DWORD vnum, long x, long y, float radius, bool
return ch;
}
// MR-8: Snow dungeon - All-damage immunity with exceptions
bool CDungeon::SpawnGroupWithVIDs(DWORD vnum, long x, long y, float radius, bool bAggressive, int count, std::vector<DWORD>& rVids)
{
LPSECTREE_MAP pkSectreeMap = SECTREE_MANAGER::instance().GetMap(m_lMapIndex);
if (pkSectreeMap == NULL) {
sys_err("CDungeon: SECTREE_MAP not found for #%ld", m_lMapIndex);
return false;
}
int iRadius = (int) radius;
int sx = pkSectreeMap->m_setting.iBaseX + x - iRadius;
int sy = pkSectreeMap->m_setting.iBaseY + y - iRadius;
int ex = sx + iRadius;
int ey = sy + iRadius;
bool bAny = false;
while (count--)
{
std::vector<DWORD> localVids;
LPCHARACTER chLeader = CHARACTER_MANAGER::instance().SpawnGroupWithVIDs(vnum, m_lMapIndex, sx, sy, ex, ey, NULL, bAggressive, this, localVids);
if (chLeader)
{
bAny = true;
if (!localVids.empty())
rVids.insert(rVids.end(), localVids.begin(), localVids.end());
}
}
return bAny;
}
LPCHARACTER CDungeon::SpawnGroupWithImmunity(DWORD vnum, long x, long y, float radius, bool bAggressive, int count, const std::vector<CHARACTER::SDamageImmunityCondition>& conditions)
{
LPSECTREE_MAP pkSectreeMap = SECTREE_MANAGER::instance().GetMap(m_lMapIndex);
if (pkSectreeMap == NULL) {
sys_err("CDungeon: SECTREE_MAP not found for #%ld", m_lMapIndex);
return NULL;
}
int iRadius = (int) radius;
int sx = pkSectreeMap->m_setting.iBaseX + x - iRadius;
int sy = pkSectreeMap->m_setting.iBaseY + y - iRadius;
int ex = sx + iRadius;
int ey = sy + iRadius;
LPCHARACTER chLeader = NULL;
while (count--)
{
LPCHARACTER ch = CHARACTER_MANAGER::instance().SpawnGroupWithImmunity(vnum, m_lMapIndex, sx, sy, ex, ey, NULL, bAggressive, this, conditions);
if (ch && !chLeader)
chLeader = ch;
}
return chLeader;
}
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
void CDungeon::SpawnRegen(const char* filename, bool bOnce)
{
if (!filename)
@@ -850,6 +951,44 @@ void CDungeon::SpawnRegen(const char* filename, bool bOnce)
regen_do(filename, m_lMapIndex, pkSectreeMap->m_setting.iBaseX, pkSectreeMap->m_setting.iBaseY, this, bOnce);
}
// MR-8: Snow dungeon - All-damage immunity with exceptions
bool CDungeon::SpawnRegenWithVIDs(const char* filename, bool bOnce, std::vector<DWORD>& rVids)
{
if (!filename)
{
sys_err("CDungeon::SpawnRegenWithVIDs(filename=NULL, bOnce=%d) - m_lMapIndex[%d]", bOnce, m_lMapIndex);
return false;
}
LPSECTREE_MAP pkSectreeMap = SECTREE_MANAGER::instance().GetMap(m_lMapIndex);
if (!pkSectreeMap)
{
sys_err("CDungeon::SpawnRegenWithVIDs(filename=%s, bOnce=%d) - m_lMapIndex[%d]", filename, bOnce, m_lMapIndex);
return false;
}
return regen_do(filename, m_lMapIndex, pkSectreeMap->m_setting.iBaseX, pkSectreeMap->m_setting.iBaseY, this, bOnce, &rVids);
}
void CDungeon::SpawnRegenWithImmunity(const char* filename, bool bOnce, const std::vector<CHARACTER::SDamageImmunityCondition>& conditions)
{
if (!filename)
{
sys_err("CDungeon::SpawnRegenWithImmunity(filename=NULL, bOnce=%d) - m_lMapIndex[%d]", bOnce, m_lMapIndex);
return;
}
LPSECTREE_MAP pkSectreeMap = SECTREE_MANAGER::instance().GetMap(m_lMapIndex);
if (!pkSectreeMap)
{
sys_err("CDungeon::SpawnRegenWithImmunity(filename=%s, bOnce=%d) - m_lMapIndex[%d]", filename, bOnce, m_lMapIndex);
return;
}
regen_do(filename, m_lMapIndex, pkSectreeMap->m_setting.iBaseX, pkSectreeMap->m_setting.iBaseY, this, bOnce, NULL, &conditions);
}
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
void CDungeon::AddRegen(LPREGEN regen)
{
regen->id = regen_id_++;

View File

@@ -1,7 +1,9 @@
#ifndef __INC_METIN_II_GAME_DUNGEON_H
#define __INC_METIN_II_GAME_DUNGEON_H
#include <vector>
#include "sectree_manager.h"
#include "char.h"
class CParty;
@@ -57,12 +59,21 @@ class CDungeon
void Spawn(DWORD vnum, const char* pos);
LPCHARACTER SpawnMob(DWORD vnum, int x, int y, int dir = 0);
LPCHARACTER SpawnMob_ac_dir(DWORD vnum, int x, int y, int dir = 0);
LPCHARACTER SpawnGroup(DWORD vnum, long x, long y, float radius, bool bAggressive=false, int count=1);
// MR-8: Snow dungeon - All-damage immunity with exceptions
LPCHARACTER SpawnMobWithImmunity(DWORD vnum, int x, int y, int dir, const std::vector<CHARACTER::SDamageImmunityCondition>& conditions);
LPCHARACTER SpawnGroup(DWORD vnum, long x, long y, float radius, bool bAggressive = false, int count = 1);
bool SpawnGroupWithVIDs(DWORD vnum, long x, long y, float radius, bool bAggressive, int count, std::vector<DWORD>& rVids);
LPCHARACTER SpawnGroupWithImmunity(DWORD vnum, long x, long y, float radius, bool bAggressive, int count, const std::vector<CHARACTER::SDamageImmunityCondition>& conditions);
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
void SpawnNameMob(DWORD vnum, int x, int y, const char* name);
void SpawnGotoMob(long lFromX, long lFromY, long lToX, long lToY);
void SpawnRegen(const char* filename, bool bOnce = true);
// MR-8: Snow dungeon - All-damage immunity with exceptions
bool SpawnRegenWithVIDs(const char* filename, bool bOnce, std::vector<DWORD>& rVids);
void SpawnRegenWithImmunity(const char* filename, bool bOnce, const std::vector<CHARACTER::SDamageImmunityCondition>& conditions);
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
void AddRegen(LPREGEN regen);
void ClearRegen();
bool IsValidRegen(LPREGEN regen, size_t regen_id);

View File

@@ -1047,6 +1047,13 @@ EVENTFUNC(quest_login_event)
}
else if (d->IsPhase(PHASE_GAME))
{
// MR-8: Fix "when_login" being loaded before character affects
if (!ch->IsLoadedAffect())
{
return PASSES_PER_SEC(1);
}
// MR-8: -- END OF -- Fix "when_login" being loaded before character affects
sys_log(0, "QUEST_LOAD: Login pc %d by event", ch->GetPlayerID());
quest::CQuestManager::instance().Login(ch->GetPlayerID());
return 0;
@@ -1110,18 +1117,12 @@ void CInputDB::QuestLoad(LPDESC d, const char * c_pData)
pkPC->SetLoaded();
pkPC->Build();
if (ch->GetDesc()->IsPhase(PHASE_GAME))
{
sys_log(0, "QUEST_LOAD: Login pc %d", pQuestTable[0].dwPID);
quest::CQuestManager::instance().Login(pQuestTable[0].dwPID);
}
else
{
quest_login_event_info* info = AllocEventInfo<quest_login_event_info>();
info->dwPID = ch->GetPlayerID();
// MR-8: Fix "when_login" being loaded before character affects
quest_login_event_info* info = AllocEventInfo<quest_login_event_info>();
info->dwPID = ch->GetPlayerID();
event_create(quest_login_event, info, PASSES_PER_SEC(1));
}
event_create(quest_login_event, info, PASSES_PER_SEC(1));
// MR-8: -- END OF -- Fix "when_login" being loaded before character affects
}
}

View File

@@ -701,17 +701,38 @@ void CInputLogin::Entergame(LPDESC d, const char * data)
else if (marriage::WeddingManager::instance().IsWeddingMap(ch->GetMapIndex()))
ch->SetWeddingMap(marriage::WeddingManager::instance().Find(ch->GetMapIndex()));
else {
// MR-8: Auto-unmount and unequip special ride seals in Nemere's Watchtower
if (ch->GetMapIndex() >= 352 * 10000 && ch->GetMapIndex() < 353 * 10000)
{
ch->RemoveAffect(AFFECT_MOUNT);
ch->RemoveAffect(AFFECT_MOUNT_BONUS);
if (ch->IsRiding() || ch->IsHorseRiding())
{
ch->StopRiding();
ch->HorseSummon(false);
}
// Check for inventory space
int emptyCell = ch->GetEmptyInventory(1);
if (emptyCell != -1)
ch->UnEquipSpecialRideUniqueItem();
}
// MR-8: -- END OF -- Auto-unmount and unequip special ride seals in Nemere's Watchtower
ch->SetDungeon(CDungeonManager::instance().FindByMapIndex(ch->GetMapIndex()));
}
}
else if (CArenaManager::instance().IsArenaMap(ch->GetMapIndex()) == true)
{
int memberFlag = CArenaManager::instance().IsMember(ch->GetMapIndex(), ch->GetPlayerID());
if (memberFlag == MEMBER_OBSERVER)
{
ch->SetObserverMode(true);
ch->SetArenaObserverMode(true);
if (CArenaManager::instance().RegisterObserverPtr(ch, ch->GetMapIndex(), ch->GetX()/100, ch->GetY()/100))
if (CArenaManager::instance().RegisterObserverPtr(ch, ch->GetMapIndex(), ch->GetX() / 100, ch->GetY() / 100))
{
sys_log(0, "ARENA : Observer add failed");
}

View File

@@ -221,44 +221,47 @@ bool ITEM_MANAGER::ReadSpecialDropItemFile(const char * c_pszFileName)
const std::string& name = pTok->at(0);
DWORD dwVnum = 0;
if (!GetVnumByOriginalName(name.c_str(), dwVnum))
// MR-8: Special_Item_Group Fix for handling gold and experience
if (name == "elk")
{
if (name == "exp")
dwVnum = CSpecialItemGroup::GOLD;
}
else if (name == "exp")
{
dwVnum = CSpecialItemGroup::EXP;
}
else if (name == "mob")
{
dwVnum = CSpecialItemGroup::MOB;
}
else if (name == "slow")
{
dwVnum = CSpecialItemGroup::SLOW;
}
else if (name == "drain_hp")
{
dwVnum = CSpecialItemGroup::DRAIN_HP;
}
else if (name == "poison")
{
dwVnum = CSpecialItemGroup::POISON;
}
else if (name == "group")
{
dwVnum = CSpecialItemGroup::MOB_GROUP;
}
else if (!GetVnumByOriginalName(name.c_str(), dwVnum))
{
str_to_number(dwVnum, name.c_str());
if (!ITEM_MANAGER::instance().GetTable(dwVnum))
{
dwVnum = CSpecialItemGroup::EXP;
}
else if (name == "mob")
{
dwVnum = CSpecialItemGroup::MOB;
}
else if (name == "slow")
{
dwVnum = CSpecialItemGroup::SLOW;
}
else if (name == "drain_hp")
{
dwVnum = CSpecialItemGroup::DRAIN_HP;
}
else if (name == "poison")
{
dwVnum = CSpecialItemGroup::POISON;
}
else if (name == "group")
{
dwVnum = CSpecialItemGroup::MOB_GROUP;
}
else
{
str_to_number(dwVnum, name.c_str());
if (!ITEM_MANAGER::instance().GetTable(dwVnum))
{
sys_err("ReadSpecialDropItemFile : there is no item %s : node %s", name.c_str(), stName.c_str());
M2_DELETE(pkGroup);
sys_err("ReadSpecialDropItemFile : there is no item %s : node %s", name.c_str(), stName.c_str());
M2_DELETE(pkGroup);
return false;
}
return false;
}
}
// MR-8: -- END OF -- Special_Item_Group Fix for handling gold and experience
int iCount = 0;
str_to_number(iCount, pTok->at(1).c_str());
@@ -286,7 +289,9 @@ bool ITEM_MANAGER::ReadSpecialDropItemFile(const char * c_pszFileName)
break;
}
loader.SetParentNode();
if (CSpecialItemGroup::QUEST == type)
{
m_map_pkQuestItemGroup.insert(std::make_pair(iVnum, pkGroup));
@@ -309,6 +314,7 @@ bool ITEM_MANAGER::ConvSpecialDropItemFile()
"%s/special_item_group.txt", LocaleService_GetBasePath().c_str());
FILE *fp = fopen("special_item_group_vnum.txt", "w");
if (!fp)
{
sys_err("could not open file (%s)", "special_item_group_vnum.txt");
@@ -372,13 +378,16 @@ bool ITEM_MANAGER::ConvSpecialDropItemFile()
if (!GetVnumByOriginalName(name.c_str(), dwVnum))
{
// MR-8: Special_Item_Group Fix for handling gold and experience
//if ( name == "<22><><EFBFBD><EFBFBD>ġ" ||
if (name == "exp" ||
if (name == "elk" ||
name == "exp" ||
name == "mob" ||
name == "slow" ||
name == "drain_hp" ||
name == "poison" ||
name == "group")
// MR-8: -- END OF -- Special_Item_Group Fix for handling gold and experience
{
dwVnum = 0;
}

View File

@@ -11,6 +11,8 @@
#include "desc_client.h"
#include "desc_manager.h"
#include <vector>
#undef sys_err
#define sys_err(fmt, ...) quest::CQuestManager::instance().QuestError(std::source_location::current(), fmt __VA_OPT__(, __VA_ARGS__))
@@ -173,6 +175,40 @@ namespace quest
return 0;
}
// MR-8: Snow dungeon - All-damage immunity with exceptions
int dungeon_regen_file_with_vids(lua_State* L)
{
if (!lua_isstring(L,1))
{
sys_err("wrong filename");
lua_newtable(L);
return 1;
}
CQuestManager& q = CQuestManager::instance();
LPDUNGEON pDungeon = q.GetCurrentDungeon();
std::vector<DWORD> vids;
if (pDungeon)
{
const char* filename = lua_tostring(L, 1);
pDungeon->SpawnRegenWithVIDs(filename, true, vids);
}
lua_newtable(L);
for (size_t i = 0; i < vids.size(); ++i)
{
lua_pushnumber(L, i + 1);
lua_pushnumber(L, vids[i]);
lua_settable(L, -3);
}
return 1;
}
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
int dungeon_set_regen_file(lua_State* L)
{
if (!lua_isstring(L,1))
@@ -971,6 +1007,207 @@ namespace quest
return 1;
}
// MR-8: Snow dungeon - All-damage immunity with exceptions
int dungeon_spawn_group_with_vids(lua_State* L)
{
//
// argument: vnum,x,y,radius,aggressive,count
// returns: table of all spawned VIDs
//
if (!lua_isnumber(L, 1) || !lua_isnumber(L, 2) || !lua_isnumber(L, 3) || !lua_isnumber(L, 4) || !lua_isnumber(L, 6))
{
sys_err("invalid argument");
lua_newtable(L);
return 1;
}
CQuestManager& q = CQuestManager::instance();
LPDUNGEON pDungeon = q.GetCurrentDungeon();
std::vector<DWORD> vids;
if (pDungeon)
{
DWORD group_vnum = (DWORD)lua_tonumber(L, 1);
long local_x = (long) lua_tonumber(L, 2) * 100;
long local_y = (long) lua_tonumber(L, 3) * 100;
float radius = (float) lua_tonumber(L, 4) * 100;
bool bAggressive = lua_toboolean(L, 5);
DWORD count = (DWORD) lua_tonumber(L, 6);
pDungeon->SpawnGroupWithVIDs(group_vnum, local_x, local_y, radius, bAggressive, count, vids);
}
// Push table of VIDs to Lua
lua_newtable(L);
for (size_t i = 0; i < vids.size(); ++i)
{
lua_pushnumber(L, i + 1);
lua_pushnumber(L, vids[i]);
lua_settable(L, -3);
}
return 1;
}
int dungeon_spawn_group_with_immunity(lua_State* L)
{
//
// argument: vnum, x, y, radius, aggressive, count, conditions_table
// conditions_table: { {type=6, value=3}, ... }
// returns: vid of group leader
//
if (!lua_isnumber(L, 1) || !lua_isnumber(L, 2) || !lua_isnumber(L, 3) ||
!lua_isnumber(L, 4) || !lua_isnumber(L, 6) || !lua_istable(L, 7))
{
sys_err("invalid argument");
lua_pushnumber(L, 0);
return 1;
}
CQuestManager& q = CQuestManager::instance();
LPDUNGEON pDungeon = q.GetCurrentDungeon();
if (!pDungeon)
{
lua_pushnumber(L, 0);
return 1;
}
DWORD group_vnum = (DWORD)lua_tonumber(L, 1);
long local_x = (long) lua_tonumber(L, 2) * 100;
long local_y = (long) lua_tonumber(L, 3) * 100;
float radius = (float) lua_tonumber(L, 4) * 100;
bool bAggressive = lua_toboolean(L, 5);
DWORD count = (DWORD) lua_tonumber(L, 6);
// Parse conditions table
std::vector<CHARACTER::SDamageImmunityCondition> conditions;
lua_pushnil(L);
while (lua_next(L, 7) != 0)
{
if (lua_istable(L, -1))
{
lua_pushstring(L, "type");
lua_gettable(L, -2);
BYTE bType = (BYTE)lua_tonumber(L, -1);
lua_pop(L, 1);
lua_pushstring(L, "value");
lua_gettable(L, -2);
DWORD dwValue = (DWORD)lua_tonumber(L, -1);
lua_pop(L, 1);
conditions.emplace_back(bType, dwValue);
}
lua_pop(L, 1);
}
LPCHARACTER ch = pDungeon->SpawnGroupWithImmunity(group_vnum, local_x, local_y, radius, bAggressive, count, conditions);
lua_pushnumber(L, ch ? ch->GetVID() : 0);
return 1;
}
int dungeon_spawn_mob_with_immunity(lua_State* L)
{
// argument: vnum, x, y, conditions_table
// returns: vid
if (!lua_isnumber(L, 1) || !lua_isnumber(L, 2) || !lua_isnumber(L, 3) || !lua_istable(L, 4))
{
sys_err("invalid argument");
lua_pushnumber(L, 0);
return 1;
}
CQuestManager& q = CQuestManager::instance();
LPDUNGEON pDungeon = q.GetCurrentDungeon();
if (!pDungeon)
{
lua_pushnumber(L, 0);
return 1;
}
DWORD mob_vnum = (DWORD)lua_tonumber(L, 1);
int x = (int)lua_tonumber(L, 2);
int y = (int)lua_tonumber(L, 3);
// Parse conditions
std::vector<CHARACTER::SDamageImmunityCondition> conditions;
lua_pushnil(L);
while (lua_next(L, 4) != 0)
{
if (lua_istable(L, -1))
{
lua_pushstring(L, "type");
lua_gettable(L, -2);
BYTE bType = (BYTE)lua_tonumber(L, -1);
lua_pop(L, 1);
lua_pushstring(L, "value");
lua_gettable(L, -2);
DWORD dwValue = (DWORD)lua_tonumber(L, -1);
lua_pop(L, 1);
conditions.emplace_back(bType, dwValue);
}
lua_pop(L, 1);
}
LPCHARACTER ch = pDungeon->SpawnMobWithImmunity(mob_vnum, x, y, 0, conditions);
lua_pushnumber(L, ch ? ch->GetVID() : 0);
return 1;
}
int dungeon_regen_file_with_immunity(lua_State* L)
{
//
// argument: filename, conditions_table
// conditions_table: { {type=6, value=3}, ... }
// returns: nothing
//
if (!lua_isstring(L, 1) || !lua_istable(L, 2))
{
sys_err("invalid argument");
return 0;
}
CQuestManager& q = CQuestManager::instance();
LPDUNGEON pDungeon = q.GetCurrentDungeon();
if (!pDungeon)
return 0;
const char* filename = lua_tostring(L, 1);
// Parse conditions table
std::vector<CHARACTER::SDamageImmunityCondition> conditions;
lua_pushnil(L);
while (lua_next(L, 2) != 0)
{
if (lua_istable(L, -1))
{
lua_pushstring(L, "type");
lua_gettable(L, -2);
BYTE bType = (BYTE)lua_tonumber(L, -1);
lua_pop(L, 1);
lua_pushstring(L, "value");
lua_gettable(L, -2);
DWORD dwValue = (DWORD)lua_tonumber(L, -1);
lua_pop(L, 1);
conditions.emplace_back(bType, dwValue);
}
lua_pop(L, 1);
}
pDungeon->SpawnRegenWithImmunity(filename, true, conditions);
return 0;
}
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
int dungeon_join(lua_State* L)
{
if (lua_gettop(L) < 1 || !lua_isnumber(L, 1))
@@ -1438,6 +1675,11 @@ namespace quest
{ "spawn_name_mob", dungeon_spawn_name_mob },
{ "spawn_goto_mob", dungeon_spawn_goto_mob },
{ "spawn_group", dungeon_spawn_group },
// MR-8: Snow dungeon - All-damage immunity with exceptions
{ "spawn_group_with_vids", dungeon_spawn_group_with_vids },
{ "spawn_group_with_immunity", dungeon_spawn_group_with_immunity },
{ "spawn_mob_with_immunity", dungeon_spawn_mob_with_immunity },
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
{ "spawn_unique", dungeon_spawn_unique },
{ "spawn_move_unique", dungeon_spawn_move_unique},
{ "spawn_move_group", dungeon_spawn_move_group},
@@ -1463,6 +1705,10 @@ namespace quest
{ "new_jump_party", dungeon_new_jump_party },
{ "new_jump", dungeon_new_jump },
{ "regen_file", dungeon_regen_file },
// MR-8: Snow dungeon - All-damage immunity with exceptions
{ "regen_file_with_vids", dungeon_regen_file_with_vids },
{ "regen_file_with_immunity", dungeon_regen_file_with_immunity },
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
{ "set_regen_file", dungeon_set_regen_file },
{ "clear_regen", dungeon_clear_regen },
{ "set_exit_all_at_eliminate", dungeon_set_exit_all_at_eliminate},

View File

@@ -8,6 +8,7 @@
#include "char_manager.h"
#include "shop_manager.h"
#include "guild.h"
#include "mob_manager.h"
namespace quest
{
@@ -350,6 +351,214 @@ namespace quest
return 0;
}
// MR-8: Damage Immunity System - Lua Bindings
int npc_set_damage_immunity(lua_State* L)
{
// npc.set_damage_immunity(vid, immune_bool)
// Sets basic immunity on/off for a specific VID
if (!lua_isnumber(L, 1))
{
lua_pushboolean(L, false);
return 1;
}
DWORD dwVID = (DWORD)lua_tonumber(L, 1);
bool bImmune = lua_toboolean(L, 2);
LPCHARACTER ch = CHARACTER_MANAGER::instance().Find(dwVID);
if (!ch)
{
sys_err("npc.set_damage_immunity: VID %u not found", dwVID);
lua_pushboolean(L, false);
return 1;
}
if (!ch->IsMonster() && !ch->IsStone() && !ch->IsDoor())
{
sys_err("npc.set_damage_immunity: VID %u is not a monster/stone/door", dwVID);
lua_pushboolean(L, false);
return 1;
}
ch->SetDamageImmunity(bImmune);
lua_pushboolean(L, true);
return 1;
}
int npc_is_damage_immune(lua_State* L)
{
// npc.is_damage_immune(vid)
// Checks if a VID has damage immunity enabled
if (!lua_isnumber(L, 1))
{
lua_pushboolean(L, false);
return 1;
}
LPCHARACTER ch = CHARACTER_MANAGER::instance().Find((DWORD)lua_tonumber(L, 1));
lua_pushboolean(L, ch ? ch->IsDamageImmune() : false);
return 1;
}
int npc_add_damage_immunity_condition(lua_State* L)
{
// npc.add_damage_immunity_condition(vid, condition_type, value, [extra_string])
// Adds a condition that must be met for attacker to damage this entity
// Types: 0=affect, 1=level_min, 2=level_max, 3=quest_flag, 4=item_equipped, 5=empire, 6=job
if (!lua_isnumber(L, 1) || !lua_isnumber(L, 2) || !lua_isnumber(L, 3))
{
lua_pushboolean(L, false);
return 1;
}
DWORD dwVID = (DWORD)lua_tonumber(L, 1);
BYTE bType = (BYTE)lua_tonumber(L, 2);
DWORD dwValue = (DWORD)lua_tonumber(L, 3);
std::string strExtra = lua_isstring(L, 4) ? lua_tostring(L, 4) : "";
LPCHARACTER ch = CHARACTER_MANAGER::instance().Find(dwVID);
if (!ch)
{
sys_err("npc.add_damage_immunity_condition: VID %u not found", dwVID);
lua_pushboolean(L, false);
return 1;
}
if (!ch->IsMonster() && !ch->IsStone() && !ch->IsDoor())
{
sys_err("npc.add_damage_immunity_condition: VID %u is not a monster/stone/door", dwVID);
lua_pushboolean(L, false);
return 1;
}
ch->AddDamageImmunityCondition(bType, dwValue, strExtra);
lua_pushboolean(L, true);
return 1;
}
int npc_clear_damage_immunity_conditions(lua_State* L)
{
// npc.clear_damage_immunity_conditions(vid)
// Removes all damage immunity conditions from a VID
if (!lua_isnumber(L, 1))
{
lua_pushboolean(L, false);
return 1;
}
LPCHARACTER ch = CHARACTER_MANAGER::instance().Find((DWORD)lua_tonumber(L, 1));
if (!ch)
{
lua_pushboolean(L, false);
return 1;
}
ch->ClearDamageImmunityConditions();
lua_pushboolean(L, true);
return 1;
}
int npc_set_damage_immunity_with_conditions(lua_State* L)
{
// npc.set_damage_immunity_with_conditions(vid, conditions_table)
// High-level function that sets immunity and conditions in one call
// Example: npc.set_damage_immunity_with_conditions(vid, {
// {type=0, value=23}, -- Need affect 23
// {type=1, value=50}, -- Need level >= 50
// {type=3, value=1, extra="dungeon.flag"} -- Need quest flag
// })
if (!lua_isnumber(L, 1) || !lua_istable(L, 2))
{
lua_pushboolean(L, false);
return 1;
}
DWORD dwVID = (DWORD)lua_tonumber(L, 1);
LPCHARACTER ch = CHARACTER_MANAGER::instance().Find(dwVID);
if (!ch)
{
sys_err("npc.set_damage_immunity_with_conditions: VID %u not found", dwVID);
lua_pushboolean(L, false);
return 1;
}
if (!ch->IsMonster() && !ch->IsStone() && !ch->IsDoor())
{
sys_err("npc.set_damage_immunity_with_conditions: VID %u is not a monster/stone/door", dwVID);
lua_pushboolean(L, false);
return 1;
}
// CRITICAL: Set immunity flag FIRST to close the race condition window
// This ensures the mob is protected immediately, even before conditions are parsed
ch->SetDamageImmunity(true);
// Clear existing conditions
ch->ClearDamageImmunityConditions();
// Parse conditions table
int condCount = 0;
lua_pushnil(L);
while (lua_next(L, 2) != 0)
{
if (lua_istable(L, -1))
{
BYTE bType = 0;
DWORD dwValue = 0;
std::string strExtra = "";
// Get 'type' field
lua_pushstring(L, "type");
lua_gettable(L, -2);
if (lua_isnumber(L, -1))
bType = (BYTE)lua_tonumber(L, -1);
lua_pop(L, 1);
// Get 'value' field
lua_pushstring(L, "value");
lua_gettable(L, -2);
if (lua_isnumber(L, -1))
dwValue = (DWORD)lua_tonumber(L, -1);
lua_pop(L, 1);
// Get 'extra' field (optional)
lua_pushstring(L, "extra");
lua_gettable(L, -2);
if (lua_isstring(L, -1))
strExtra = lua_tostring(L, -1);
lua_pop(L, 1);
ch->AddDamageImmunityCondition(bType, dwValue, strExtra);
condCount++;
}
lua_pop(L, 1);
}
// Note: Immunity flag was already set at the start to minimize race condition
// If no conditions were added, the mob will block ALL damage (fail-safe)
lua_pushboolean(L, true);
return 1;
}
// MR-8: -- END OF -- Damage Immunity System - Lua Bindings
void RegisterNPCFunctionTable()
{
luaL_reg npc_functions[] =
@@ -379,6 +588,15 @@ namespace quest
{ "dec_remain_skill_book_count", npc_dec_remain_skill_book_count },
{ "get_remain_hairdye_count", npc_get_remain_hairdye_count },
{ "dec_remain_hairdye_count", npc_dec_remain_hairdye_count },
// MR-8: Damage Immunity System - Lua Bindings
{ "set_damage_immunity", npc_set_damage_immunity },
{ "is_damage_immune", npc_is_damage_immune },
{ "add_damage_immunity_condition", npc_add_damage_immunity_condition },
{ "clear_damage_immunity_conditions", npc_clear_damage_immunity_conditions },
{ "set_damage_immunity_with_conditions", npc_set_damage_immunity_with_conditions },
// MR-8: -- END OF -- Damage Immunity System - Lua Bindings
{ NULL, NULL }
};

View File

@@ -266,7 +266,8 @@ namespace quest
int pc_in_dungeon(lua_State * L)
{
LPCHARACTER ch = CQuestManager::instance().GetCurrentCharacterPtr();
lua_pushboolean(L, ch->GetDungeon()?1:0);
lua_pushboolean(L, ch->GetDungeon() ? 1 : 0);
return 1;
}
@@ -1245,6 +1246,18 @@ namespace quest
if (!lua_isnumber(L, 1))
return 0;
LPCHARACTER ch = CQuestManager::instance().GetCurrentCharacterPtr();
// MR-8: Prevent mounting in Nemere's Watchtower
long lMapIndex = ch->GetMapIndex();
if (lMapIndex >= 352 * 10000 && lMapIndex < 353 * 10000)
{
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You cannot ride your horse in Nemere's Watchtower."));
return 0;
}
// MR-8: -- END OF -- Prevent mounting in Nemere's Watchtower
int length = 60;
if (lua_isnumber(L, 2))
@@ -1255,8 +1268,6 @@ namespace quest
if (length < 0)
length = 60;
LPCHARACTER ch = CQuestManager::instance().GetCurrentCharacterPtr();
ch->RemoveAffect(AFFECT_MOUNT);
ch->RemoveAffect(AFFECT_MOUNT_BONUS);
@@ -1310,11 +1321,16 @@ namespace quest
LPCHARACTER ch = CQuestManager::instance().GetCurrentCharacterPtr();
if( NULL != ch )
// MR-8: Prevent mount bonuses in Nemere's Watchtower
long lMapIndex = ch->GetMapIndex();
bool isInNemereDungeon = lMapIndex >= 352 * 10000 && lMapIndex < 353 * 10000;
if (NULL != ch && !isInNemereDungeon)
{
ch->RemoveAffect(AFFECT_MOUNT_BONUS);
ch->AddAffect(AFFECT_MOUNT_BONUS, aApplyInfo[applyOn].bPointType, value, AFF_NONE, duration, 0, false);
}
// MR-8: -- END OF -- Prevent mount bonuses in Nemere's Watchtower
return 0;
}
@@ -1322,10 +1338,13 @@ namespace quest
int pc_unmount(lua_State* L)
{
LPCHARACTER ch = CQuestManager::instance().GetCurrentCharacterPtr();
ch->RemoveAffect(AFFECT_MOUNT);
ch->RemoveAffect(AFFECT_MOUNT_BONUS);
if (ch->IsHorseRiding())
ch->StopRiding();
return 0;
}

View File

@@ -253,7 +253,9 @@ bool is_regen_exception(long x, long y)
return false;
}
static void regen_spawn_dungeon(LPREGEN regen, LPDUNGEON pDungeon, bool bOnce)
// MR-8: Snow dungeon - All-damage immunity with exceptions
static void regen_spawn_dungeon(LPREGEN regen, LPDUNGEON pDungeon, bool bOnce, std::vector<DWORD>* pOutVids, const std::vector<CHARACTER::SDamageImmunityCondition>* pConditions)
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
{
DWORD num;
DWORD i;
@@ -275,6 +277,20 @@ static void regen_spawn_dungeon(LPREGEN regen, LPDUNGEON pDungeon, bool bOnce)
{
++regen->count;
ch->SetDungeon(pDungeon);
// MR-8: Snow dungeon - All-damage immunity with exceptions
// Apply damage immunity atomically before mob is attackable
if (pConditions && !pConditions->empty())
{
ch->SetDamageImmunity(true);
for (const auto& cond : *pConditions)
ch->AddDamageImmunityCondition(cond.bType, cond.dwValue, cond.strExtra);
}
if (pOutVids)
pOutVids->push_back(ch->GetVID());
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
}
}
else if (regen->sx == regen->ex && regen->sy == regen->ey)
@@ -291,6 +307,20 @@ static void regen_spawn_dungeon(LPREGEN regen, LPDUNGEON pDungeon, bool bOnce)
{
++regen->count;
ch->SetDungeon(pDungeon);
// MR-8: Snow dungeon - All-damage immunity with exceptions
// Apply damage immunity atomically before mob is attackable
if (pConditions && !pConditions->empty())
{
ch->SetDamageImmunity(true);
for (const auto& cond : *pConditions)
ch->AddDamageImmunityCondition(cond.bType, cond.dwValue, cond.strExtra);
}
if (pOutVids)
pOutVids->push_back(ch->GetVID());
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
}
}
else
@@ -303,12 +333,53 @@ static void regen_spawn_dungeon(LPREGEN regen, LPDUNGEON pDungeon, bool bOnce)
{
++regen->count;
ch->SetDungeon(pDungeon);
// MR-8: Snow dungeon - All-damage immunity with exceptions
// Apply damage immunity atomically before mob is attackable
if (pConditions && !pConditions->empty())
{
ch->SetDamageImmunity(true);
for (const auto& cond : *pConditions)
ch->AddDamageImmunityCondition(cond.bType, cond.dwValue, cond.strExtra);
}
if (pOutVids)
pOutVids->push_back(ch->GetVID());
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
}
}
else if (regen->type == REGEN_TYPE_GROUP)
{
if (CHARACTER_MANAGER::Instance().SpawnGroup(regen->vnum, regen->lMapIndex, regen->sx, regen->sy, regen->ex, regen->ey, bOnce ? NULL : regen, regen->is_aggressive, pDungeon))
// MR-8: Snow dungeon - All-damage immunity with exceptions
std::vector<DWORD> localVids;
LPCHARACTER leader = CHARACTER_MANAGER::Instance().SpawnGroupWithVIDs(regen->vnum, regen->lMapIndex, regen->sx, regen->sy, regen->ex, regen->ey, bOnce ? NULL : regen, regen->is_aggressive, pDungeon, localVids);
if (leader)
{
++regen->count;
// Apply damage immunity to all spawned group members if requested.
if (pConditions && !pConditions->empty())
{
for (DWORD vid : localVids)
{
LPCHARACTER member = CHARACTER_MANAGER::instance().Find(vid);
if (!member)
continue;
member->SetDamageImmunity(true);
for (const auto& cond : *pConditions)
member->AddDamageImmunityCondition(cond.bType, cond.dwValue, cond.strExtra);
}
}
if (pOutVids)
pOutVids->insert(pOutVids->end(), localVids.begin(), localVids.end());
}
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
}
else if (regen->type == REGEN_TYPE_GROUP_GROUP)
{
@@ -403,11 +474,15 @@ EVENTFUNC(dungeon_regen_event)
regen->event = NULL;
}
regen_spawn_dungeon(regen, pDungeon, false);
// MR-8: Snow dungeon - All-damage immunity with exceptions
regen_spawn_dungeon(regen, pDungeon, false, NULL, NULL);
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
return PASSES_PER_SEC(regen->time);
}
bool regen_do(const char* filename, long lMapIndex, int base_x, int base_y, LPDUNGEON pDungeon, bool bOnce)
// MR-8:Snow dungeon - All-damage immunity with exceptions
bool regen_do(const char* filename, long lMapIndex, int base_x, int base_y, LPDUNGEON pDungeon, bool bOnce, std::vector<DWORD>* pOutVids, const std::vector<CHARACTER::SDamageImmunityCondition>* pConditions)
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
{
if (g_bNoRegen)
return true;
@@ -500,8 +575,10 @@ bool regen_do(const char* filename, long lMapIndex, int base_x, int base_y, LPDU
// before the call to CHARACTER::SetRegen()
}
// 처음엔 무조건 리젠 해준다.
regen_spawn_dungeon(regen, pDungeon, bOnce);
// MR-8: Snow dungeon - All-damage immunity with exceptions
// 처음엔 무조건 리젠 해준다. Optionally collect VIDs for the initial spawn.
regen_spawn_dungeon(regen, pDungeon, bOnce, pOutVids, pConditions);
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
}
}

View File

@@ -1,4 +1,10 @@
#include "dungeon.h"
#include <vector>
#include "dungeon.h"
// MR-8: Snow dungeon - All-damage immunity with exceptions
// Forward declaration
class CHARACTER;
// MR-8: -- END OF -- Snow dungeon - All-damage immunity with exceptions
enum
{
@@ -84,7 +90,9 @@ EVENTINFO(dungeon_regen_event_info)
};
extern bool regen_load(const char *filename, long lMapIndex, int base_x, int base_y);
extern bool regen_do(const char* filename, long lMapIndex, int base_x, int base_y, LPDUNGEON pDungeon, bool bOnce = true );
// MR-8: Snow dungeon - All-damage immunity with exceptions
extern bool regen_do(const char* filename, long lMapIndex, int base_x, int base_y, LPDUNGEON pDungeon, bool bOnce = true, std::vector<DWORD>* pOutVids = NULL, const std::vector<CHARACTER::SDamageImmunityCondition>* pConditions = NULL);
// MR-8: --END OF -- Snow dungeon - All-damage immunity with exceptions
extern bool regen_load_in_file(const char* filename, long lMapIndex, int base_x, int base_y );
extern void regen_free();