- Mesaj
- 258
- Çözümler
- 5
- Beğeni
- 304
- Puan
- 829
- Ticaret Puanı
- 0
Herkese merhabalar,
Official sistemlere olan aşkım ve ileride gerçekleştirmek istediğim Official 1-120 tadında sunucum için sistemler geliştirmeye devam ediyorum. En son yapmaya çalıştığımda Dodge skill sistemini animasyon yaratma konusunda ki yetersiz bilgimden dolayı yarıda bıraktım ve rafa kaldırdım. Bu sefer zaten daha öncede yapılmış olan sistemlerden yola çıkarak Official Alastor Zindanını yapıyorum. Bunu yaparken farklı bir forumda Rakancito adlı arkadaşın aynı sistem için C++,Lua ve Game tarafında kullandığı dosyalardan esinlenerek sıfırdan tamamen Yapay Zeka tarafından yazılmış haliyle kendim yapıyorum. Ayrıca oyunun içerisinde mevcut olan SnakeLair (Nethis) gibi dungeonların sistemlerdende esinlenerek geliştiriyorum.
Sistemi çoğu kişi biliyor ancak bilmeyenler için Metin2'nin zamanında getirdiği bossların Wikisi:
İncelemek isterseniz şu ana kadar yazdığım kodların bir kısmı ve ayrıca bir videoda ekliyorum. Şu an için Alastor skillerinin mantıkları ufak ufak dosyada kodlandı. Mevcut Dungeon fonksiyonları dışında kendi özel dungeon fonksiyonlarıda var. Eğer önerileriniz olursa mutlaka konu altından yazabilirsiniz. Efektler henüz yok dediğim gibi bilgim az olduğundan onları ilerleyen zamanlarda tamamlayacağım. Şu an zindan yöneticisi ve quest taraflarını tamamlamaya çalışıyorum. Ufak tefek hatalar olacaktır mesela başlangıçta Alastor'un neden görünmez olduğu... Başlangıç efektinden kaynaklı olabilir. Hepsini çözücez yavaş yavaş
Sistemin Videosu:
Official sistemlere olan aşkım ve ileride gerçekleştirmek istediğim Official 1-120 tadında sunucum için sistemler geliştirmeye devam ediyorum. En son yapmaya çalıştığımda Dodge skill sistemini animasyon yaratma konusunda ki yetersiz bilgimden dolayı yarıda bıraktım ve rafa kaldırdım. Bu sefer zaten daha öncede yapılmış olan sistemlerden yola çıkarak Official Alastor Zindanını yapıyorum. Bunu yaparken farklı bir forumda Rakancito adlı arkadaşın aynı sistem için C++,Lua ve Game tarafında kullandığı dosyalardan esinlenerek sıfırdan tamamen Yapay Zeka tarafından yazılmış haliyle kendim yapıyorum. Ayrıca oyunun içerisinde mevcut olan SnakeLair (Nethis) gibi dungeonların sistemlerdende esinlenerek geliştiriyorum.
Sistemi çoğu kişi biliyor ancak bilmeyenler için Metin2'nin zamanında getirdiği bossların Wikisi:
Linkleri görebilmek için
giriş yap veya kayıt ol.
Linkleri görebilmek için
giriş yap veya kayıt ol.
Linkleri görebilmek için
giriş yap veya kayıt ol.
İncelemek isterseniz şu ana kadar yazdığım kodların bir kısmı ve ayrıca bir videoda ekliyorum. Şu an için Alastor skillerinin mantıkları ufak ufak dosyada kodlandı. Mevcut Dungeon fonksiyonları dışında kendi özel dungeon fonksiyonlarıda var. Eğer önerileriniz olursa mutlaka konu altından yazabilirsiniz. Efektler henüz yok dediğim gibi bilgim az olduğundan onları ilerleyen zamanlarda tamamlayacağım. Şu an zindan yöneticisi ve quest taraflarını tamamlamaya çalışıyorum. Ufak tefek hatalar olacaktır mesela başlangıçta Alastor'un neden görünmez olduğu... Başlangıç efektinden kaynaklı olabilir. Hepsini çözücez yavaş yavaş
Sistemin Videosu:
WhiteDragon.cpp:
#include "stdafx.h"
#include "WhiteDragon.h"
#include "char_manager.h"
#include "party.h"
#include "packet.h"
#include "regen.h"
#include "start_position.h"
namespace WhiteDragon
{
EVENTINFO(wd_spawn_info)
{
CMap* pMap;
BYTE step;
bool hard;
};
EVENTFUNC(wd_spawn_event)
{
wd_spawn_info* info = dynamic_cast<wd_spawn_info*>(event->info);
if (!info || !info->pMap)
{
sys_log(0, "[WhiteDragon][SPAWN_EVENT] NULL info or pMap");
return 0;
}
sys_log(0, "[WhiteDragon][SPAWN_EVENT] step=%d hard=%d", info->step, info->hard);
// step==2: yumurta -> 2sn sonra boss spawn
if (info->step == 2)
{
DWORD boss = info->hard ? WD_BOSS_HARD_VNUM : WD_BOSS_EASY_VNUM;
sys_log(0, "[WhiteDragon][SPAWN_EVENT] Spawning boss vnum=%u at 131,119", boss);
LPCHARACTER pk = info->pMap->Spawn(boss, 131, 119, 0, true);
if (pk)
sys_log(0, "[WhiteDragon][SPAWN_EVENT] Boss spawned VID=%u", (DWORD)pk->GetVID());
else
sys_log(0, "[WhiteDragon][SPAWN_EVENT] Boss spawn FAILED");
}
return 0;
}
EVENTINFO(wd_limit_info)
{
CMap* pMap;
};
EVENTINFO(wd_warning_info)
{
CMap* pMap;
int minutes;
};
EVENTFUNC(wd_warning_event)
{
wd_warning_info* info = dynamic_cast<wd_warning_info*>(event->info);
if (info && info->pMap)
{
char szNotice[512];
snprintf(szNotice, sizeof(szNotice), "%d minute(s) remaining!", info->minutes);
SendNoticeMap(szNotice, info->pMap->GetMapIndex(), true);
}
return 0;
}
EVENTINFO(wd_wave_info)
{
CMap* pMap; BYTE wave; bool hard;
};
EVENTFUNC(wd_wave_event)
{
wd_wave_info* info = dynamic_cast<wd_wave_info*>(event->info);
if (!info || !info->pMap)
return 0;
info->pMap->OnWave(info->wave, info->hard);
return 0;
}
EVENTINFO(wd_spike_info)
{
CMap* pMap;
int tick;
};
EVENTINFO(wd_coldscan_info)
{
CMap* pMap;
};
EVENTFUNC(wd_coldscan_event)
{
wd_coldscan_info* info = dynamic_cast<wd_coldscan_info*>(event->info);
if (!info || !info->pMap)
return 0;
// Oyuncuların soğuk alan içinde olup olmadığını işaretle
if (info->pMap->GetMapSectree())
{
struct FMark { WhiteDragon::CMap* map; FMark(WhiteDragon::CMap* m):map(m){} void operator()(LPENTITY e){
if (!e->IsType(ENTITY_CHARACTER)) return; LPCHARACTER ch=(LPCHARACTER)e; if (!ch->IsPC()||ch->IsDead()) return;
bool inside=false; int cx,cy; for (auto &c: map->GetColdCenters()){ cx=map->GetMapSectree()->m_setting.iBaseX + c.first*100; cy=map->GetMapSectree()->m_setting.iBaseY + c.second*100; int dx=ch->GetX()-cx; int dy=ch->GetY()-cy; if (dx*dx+dy*dy <= (WD_COLD_RADIUS*WD_COLD_RADIUS)){ inside=true; break; } }
map->SetColdInside((DWORD)ch->GetVID(), inside); } } f(info->pMap);
info->pMap->GetMapSectree()->for_each(f);
}
return PASSES_PER_SEC(1);
}
EVENTINFO(wd_coldtick_info)
{
CMap* pMap; int hard; int tick10s;
};
EVENTFUNC(wd_coldtick_event)
{
wd_coldtick_info* info = dynamic_cast<wd_coldtick_info*>(event->info);
if (!info || !info->pMap)
return 0;
// 10 saniyede bir stack hasarı uygula, alana girildiğinde anlık hasar ver
int maxStack = info->hard ? 7 : 5;
int perTickPct = info->hard ? 7 : 5;
if (info->pMap->GetMapSectree())
{
struct FApply { WhiteDragon::CMap* map; int maxStack; int perTickPct; FApply(WhiteDragon::CMap* m,int ms,int pp):map(m),maxStack(ms),perTickPct(pp){} void operator()(LPENTITY e){
if (!e->IsType(ENTITY_CHARACTER)) return; LPCHARACTER ch=(LPCHARACTER)e; if (!ch->IsPC()||ch->IsDead()) return; DWORD vid=(DWORD)ch->GetVID();
bool inside = map->IsColdInside(vid);
if (inside)
{
// anlık hasar (giriş anında): 15k/20k – basit yaklaşım: her saniye içerideyse 15k/20k uygula yerine sadece ilk girişte. Şimdilik her işaretlemede tek seferlik uygula.
if (!map->HasColdStack(vid))
{
int instant = map->IsHard() ? 20000 : 15000;
ch->PointChange(POINT_HP, -instant);
}
int cur = map->GetColdStack(vid);
if (cur < maxStack) map->SetColdStack(vid, cur + 1);
}
else
{
map->ClearColdStack(vid);
}
} } f(info->pMap, maxStack, perTickPct);
info->pMap->GetMapSectree()->for_each(f);
}
// 10 sn stack hasarı
info->tick10s += 1;
if (info->tick10s >= 10)
{
info->tick10s = 0;
if (info->pMap->GetMapSectree())
{
struct FTick { WhiteDragon::CMap* map; int perTickPct; FTick(WhiteDragon::CMap* m,int p):map(m),perTickPct(p){} void operator()(LPENTITY e){
if (!e->IsType(ENTITY_CHARACTER)) return; LPCHARACTER ch=(LPCHARACTER)e; if (!ch->IsPC()||ch->IsDead()) return; DWORD vid=(DWORD)ch->GetVID();
int stacks = map->GetColdStack(vid); if (stacks<=0) return;
int dmg = (ch->GetMaxHP() * perTickPct * stacks) / 100; ch->PointChange(POINT_HP, -dmg);
} } f(info->pMap, perTickPct);
info->pMap->GetMapSectree()->for_each(f);
}
}
return PASSES_PER_SEC(1);
}
EVENTFUNC(wd_spike_event)
{
wd_spike_info* info = dynamic_cast<wd_spike_info*>(event->info);
if (!info || !info->pMap)
return 0;
sys_log(0, "[WhiteDragon][SPIKE] tick=%d", info->tick);
// Spike uyarı notice
char szNotice[512];
snprintf(szNotice, sizeof(szNotice), "Ice spikes are erupting from the ground!");
SendNoticeMap(szNotice, info->pMap->GetMapIndex(), true);
if (info->pMap->GetMapSectree())
{
int hitCount = 0;
struct FSpike
{
int& count;
FSpike(int& c):count(c){}
void operator()(LPENTITY e)
{
if (!e->IsType(ENTITY_CHARACTER)) return;
LPCHARACTER ch = (LPCHARACTER)e;
if (!ch->IsPC() || ch->IsDead()) return;
// Wiki: "Karakterin toplam HP'sinin %50'si"
int drop = ch->GetMaxHP() / 2;
ch->PointChange(POINT_HP, -drop);
ch->EffectPacket(SE_EFFECT_SMH_CIRCLE_THORN);
count++;
}
} f(hitCount);
info->pMap->GetMapSectree()->for_each(f);
sys_log(0, "[WhiteDragon][SPIKE] Hit %d players with 50%% HP dmg", hitCount);
}
info->tick++;
return PASSES_PER_SEC(15);
}
EVENTFUNC(wd_limit_event)
{
wd_limit_info* info = dynamic_cast<wd_limit_info*>(event->info);
if (info && info->pMap)
{
// Zaman doldu notice
char szNotice[512];
snprintf(szNotice, sizeof(szNotice), "Time's up! You will be warped out now.");
SendNoticeMap(szNotice, info->pMap->GetMapIndex(), true);
info->pMap->EndDungeonWarp();
}
return 0;
}
CMap::CMap(long lMapIndex, bool bHard)
{
sys_log(0, "[WhiteDragon][CTOR] Creating CMap for index=%ld hard=%d", lMapIndex, bHard);
// Event'leri sıfırla
m_evSpawn = NULL;
m_evLimit = NULL;
m_evWave1 = m_evWave2 = m_evWave3 = NULL;
m_evSpike = m_evColdScan = m_evColdTick = NULL;
// Yumurta pointer'ını sıfırla
m_pkEgg = NULL;
// Temel ayarlar
SetDungeonStep(1);
SetMapIndex(lMapIndex);
SetMapSectree(SECTREE_MANAGER::instance().GetMap(lMapIndex));
SetHard(bHard);
if (!GetMapSectree())
sys_log(0, "[WhiteDragon][CTOR] WARNING: Sectree is NULL for map %ld", lMapIndex);
else
sys_log(0, "[WhiteDragon][CTOR] Sectree OK for map %ld", lMapIndex);
BuildColdAreas();
// Snake Lair mantığında: Start() içinde portal spawn + limit timer
Start();
sys_log(0, "[WhiteDragon][CTOR] CMap construction complete");
}
CMap::~CMap()
{
if (m_evSpawn) event_cancel(&m_evSpawn);
if (m_evLimit) event_cancel(&m_evLimit);
if (m_evSpike) event_cancel(&m_evSpike);
if (m_evColdScan) event_cancel(&m_evColdScan);
if (m_evColdTick) event_cancel(&m_evColdTick);
if (m_evWave1) event_cancel(&m_evWave1);
if (m_evWave2) event_cancel(&m_evWave2);
if (m_evWave3) event_cancel(&m_evWave3);
m_evSpawn = m_evLimit = NULL;
}
void CMap::Destroy()
{
if (m_evSpawn) event_cancel(&m_evSpawn);
if (m_evLimit) event_cancel(&m_evLimit);
if (m_evSpike) event_cancel(&m_evSpike);
if (m_evColdScan) event_cancel(&m_evColdScan);
if (m_evColdTick) event_cancel(&m_evColdTick);
if (m_evWave1) event_cancel(&m_evWave1);
if (m_evWave2) event_cancel(&m_evWave2);
if (m_evWave3) event_cancel(&m_evWave3);
m_evSpawn = m_evLimit = NULL;
SetDungeonStep(1);
SetMapIndex(0);
SetMapSectree(NULL);
SetParty(NULL);
}
void CMap::StartDungeon(LPCHARACTER pkChar)
{
sys_log(0, "[WhiteDragon] CMap::StartDungeon called");
// Parti ve lider kontrolü (Snake Lair mantığı)
LPPARTY pParty = pkChar->GetParty();
if (!pParty)
{
sys_log(0, "[WhiteDragon] StartDungeon: No party");
return;
}
SetParty(pParty);
if (pParty->GetLeaderPID() != pkChar->GetPlayerID())
{
pkChar->ChatPacket(CHAT_TYPE_INFO, "Only the party leader can start the dungeon.");
sys_log(0, "[WhiteDragon] StartDungeon: Not leader");
return;
}
// Notice gönder
char szNotice[512];
snprintf(szNotice, sizeof(szNotice), "The dungeon will be available for 30 minutes.");
SendNoticeMap(szNotice, pkChar->GetMapIndex(), true);
// Yumurtayı yok et (SnakeLair mantığı)
if (GetEgg())
{
sys_log(0, "[WhiteDragon] Destroying egg VID=%u", (DWORD)GetEgg()->GetVID());
GetEgg()->Dead();
SetEgg(NULL);
}
else
{
sys_log(0, "[WhiteDragon] WARNING: No egg to destroy!");
}
sys_log(0, "[WhiteDragon] StartDungeon: Setting step to 2");
SetDungeonStep(2);
sys_log(0, "[WhiteDragon] StartDungeon complete");
}
void CMap::SetDungeonStep(BYTE bStep)
{
sys_log(0, "[WhiteDragon] SetDungeonStep: %d", bStep);
m_bStep = bStep;
if (m_evSpawn) event_cancel(&m_evSpawn);
if (bStep == 2)
{
// Step 2: Boss spawn + 12 soğuk alan + wave timers
sys_log(0, "[WhiteDragon] Step 2: Starting boss spawn sequence");
// Notice: Alastor uyanıyor
char szNotice[512];
snprintf(szNotice, sizeof(szNotice), "Alastor is awakening... Prepare for battle!");
SendNoticeMap(szNotice, GetMapIndex(), true);
wd_spawn_info* si = AllocEventInfo<wd_spawn_info>();
si->pMap = this;
si->step = bStep;
si->hard = IsHard();
m_evSpawn = event_create(wd_spawn_event, si, PASSES_PER_SEC(2));
// 12 Ölümcül Soğuk alanları spawn et (NPC olarak, statik)
SpawnColdAreas();
// Wave zamanlayıcıları (StartDungeon'dan buraya taşındı)
wd_wave_info* w1 = AllocEventInfo<wd_wave_info>(); w1->pMap = this; w1->wave = 1; w1->hard = IsHard();
wd_wave_info* w2 = AllocEventInfo<wd_wave_info>(); w2->pMap = this; w2->wave = 2; w2->hard = IsHard();
wd_wave_info* w3 = AllocEventInfo<wd_wave_info>(); w3->pMap = this; w3->wave = 3; w3->hard = IsHard();
int t1 = IsHard() ? (WD_TIME_LIMIT_SECONDS - 1710) : (WD_TIME_LIMIT_SECONDS - 1650);
int t2 = IsHard() ? (WD_TIME_LIMIT_SECONDS - 1620) : (WD_TIME_LIMIT_SECONDS - 1500);
int t3 = IsHard() ? (WD_TIME_LIMIT_SECONDS - 1530) : (WD_TIME_LIMIT_SECONDS - 1350);
m_evWave1 = event_create(wd_wave_event, w1, PASSES_PER_SEC(t1));
m_evWave2 = event_create(wd_wave_event, w2, PASSES_PER_SEC(t2));
m_evWave3 = event_create(wd_wave_event, w3, PASSES_PER_SEC(t3));
// Soğuk alan tick'leri
wd_coldscan_info* ci = AllocEventInfo<wd_coldscan_info>(); ci->pMap = this;
m_evColdScan = event_create(wd_coldscan_event, ci, PASSES_PER_SEC(1));
wd_coldtick_info* ti = AllocEventInfo<wd_coldtick_info>(); ti->pMap = this; ti->hard = IsHard() ? 1 : 0; ti->tick10s = 0;
m_evColdTick = event_create(wd_coldtick_event, ti, PASSES_PER_SEC(1));
// Buz dikenleri (15 saniyede bir, tüm oyunculara %50 HP hasarı)
wd_spike_info* spike = AllocEventInfo<wd_spike_info>();
spike->pMap = this; spike->tick = 0;
m_evSpike = event_create(wd_spike_event, spike, PASSES_PER_SEC(15));
sys_log(0, "[WhiteDragon] Step 2: All timers and events set");
}
}
void CMap::OnKill(LPCHARACTER pkMonster, LPCHARACTER pKiller)
{
if (!pkMonster)
return;
DWORD v = pkMonster->GetRaceNum();
sys_log(0, "[WhiteDragon][ONKILL] Monster %u killed by %s", v, pKiller?pKiller->GetName():"NULL");
if (v == WD_BOSS_EASY_VNUM || v == WD_BOSS_HARD_VNUM)
{
sys_log(0, "[WhiteDragon][ONKILL] BOSS KILLED! Starting 60s warp timer");
// Boss öldürüldü notice
char szNotice[512];
snprintf(szNotice, sizeof(szNotice), "Alastor has been defeated! You will be warped out in 60 seconds.");
SendNoticeMap(szNotice, GetMapIndex(), true);
if (m_evLimit)
event_cancel(&m_evLimit);
wd_limit_info* li = AllocEventInfo<wd_limit_info>();
li->pMap = this;
m_evLimit = event_create(wd_limit_event, li, PASSES_PER_SEC(60));
if (m_evWave1) event_cancel(&m_evWave1);
if (m_evWave2) event_cancel(&m_evWave2);
if (m_evWave3) event_cancel(&m_evWave3);
if (m_evSpike) event_cancel(&m_evSpike);
sys_log(0, "[WhiteDragon][ONKILL] All events cancelled, 60s until EndDungeonWarp");
}
}
void CMap::EndDungeonWarp()
{
sys_log(0, "[WhiteDragon][END] EndDungeonWarp called for map %ld", GetMapIndex());
if (GetMapSectree())
{
int warpCount = 0;
struct FExit
{
int& count;
FExit(int& c):count(c){}
void operator()(LPENTITY e)
{
if (!e->IsType(ENTITY_CHARACTER)) return;
LPCHARACTER ch = (LPCHARACTER)e;
if (!ch->IsPC()) return;
PIXEL_POSITION pos;
if (SECTREE_MANAGER::instance().GetRecallPositionByEmpire(WD_MAP_BOSS, 0, pos))
{
ch->WarpSet(pos.x, pos.y);
count++;
}
}
} f(warpCount);
GetMapSectree()->for_each(f);
sys_log(0, "[WhiteDragon][END] Warped %d players out", warpCount);
}
long idx = GetMapIndex();
sys_log(0, "[WhiteDragon][END] Destroying private map %ld", idx);
SECTREE_MANAGER::instance().DestroyPrivateMap(idx);
Destroy();
CWD::instance().Remove(idx);
M2_DELETE(this);
}
void CMap::Start()
{
sys_log(0, "[WhiteDragon][START] CMap::Start called");
if (!GetMapSectree())
{
sys_log(0, "[WhiteDragon][START] No sectree, calling EndDungeonWarp");
EndDungeonWarp();
return;
}
// Snake Lair mantığı: Portal NPC spawn + limit timer
sys_log(0, "[WhiteDragon][START] Spawning portal/egg vnum=%u at 131,144", WD_EGG_VNUM);
LPCHARACTER egg = Spawn(WD_EGG_VNUM, 131, 144, 0, true);
if (egg)
{
SetEgg(egg); // Yumurtayı kaydet
sys_log(0, "[WhiteDragon][START] Portal/Egg spawned VID=%u", (DWORD)egg->GetVID());
}
else
sys_log(0, "[WhiteDragon][START] Portal/Egg spawn FAILED");
// Limit timer (30 dakika)
if (m_evLimit) event_cancel(&m_evLimit);
wd_limit_info* li = AllocEventInfo<wd_limit_info>();
li->pMap = this;
m_evLimit = event_create(wd_limit_event, li, PASSES_PER_SEC(WD_TIME_LIMIT_SECONDS));
sys_log(0, "[WhiteDragon][START] Limit timer set: 30 minutes");
// Zaman uyarıları (10, 5, 1 dakika)
wd_warning_info* w10 = AllocEventInfo<wd_warning_info>();
w10->pMap = this; w10->minutes = 10;
event_create(wd_warning_event, w10, PASSES_PER_SEC(20 * 60)); // 20 dakika sonra (30-10=20)
wd_warning_info* w5 = AllocEventInfo<wd_warning_info>();
w5->pMap = this; w5->minutes = 5;
event_create(wd_warning_event, w5, PASSES_PER_SEC(25 * 60)); // 25 dakika sonra (30-5=25)
wd_warning_info* w1 = AllocEventInfo<wd_warning_info>();
w1->pMap = this; w1->minutes = 1;
event_create(wd_warning_event, w1, PASSES_PER_SEC(29 * 60)); // 29 dakika sonra (30-1=29)
sys_log(0, "[WhiteDragon][START] Time warnings set: 10, 5, 1 minutes");
}
LPCHARACTER CMap::Spawn(DWORD vnum, int x, int y, int dir, bool motion)
{
if (!GetMapSectree() || vnum == 0) return NULL;
return CHARACTER_MANAGER::instance().SpawnMob(vnum, GetMapIndex(),
GetMapSectree()->m_setting.iBaseX + x * 100,
GetMapSectree()->m_setting.iBaseY + y * 100,
0, motion, dir == 0 ? -1 : (dir - 1) * 45);
}
void CMap::OnWave(BYTE wave, bool hard)
{
sys_log(0, "[WhiteDragon][WAVE] Wave %d triggered, mode=%s", wave, hard?"HARD":"EASY");
// Wave notice
char szNotice[512];
if (wave == 1)
snprintf(szNotice, sizeof(szNotice), "Alastor's Rage unleashed! A horde of monsters approaches!");
else if (wave == 2)
snprintf(szNotice, sizeof(szNotice), "Alastor's Rage unleashed again! Be ready!");
else if (wave == 3)
snprintf(szNotice, sizeof(szNotice), "Alastor's final Rage! This is the last wave!");
else
snprintf(szNotice, sizeof(szNotice), "A wave of monsters approaches!");
SendNoticeMap(szNotice, GetMapIndex(), true);
DWORD bossV = hard ? WD_BOSS_HARD_VNUM : WD_BOSS_EASY_VNUM;
LPSECTREE_MAP p = GetMapSectree();
if (!p)
{
sys_log(0, "[WhiteDragon][WAVE] No sectree");
return;
}
LPCHARACTER boss = NULL;
struct FFindBoss { DWORD vnum; LPCHARACTER& out; FFindBoss(DWORD v, LPCHARACTER& o):vnum(v),out(o){} void operator()(LPENTITY e){ if (out) return; if (!e->IsType(ENTITY_CHARACTER)) return; LPCHARACTER c=(LPCHARACTER)e; if (!c->IsNPC()) return; if (c->GetRaceNum()==vnum) out=c; } } finder(bossV,boss);
p->for_each(finder);
if (boss)
{
int healPct = hard ? WD_HEAL_HARD_PCT : WD_HEAL_EASY_PCT;
int add = (boss->GetMaxHP() * healPct) / 100;
boss->PointChange(POINT_HP, add);
sys_log(0, "[WhiteDragon][WAVE] Boss healed %d%% (%d HP)", healPct, add);
}
else
{
sys_log(0, "[WhiteDragon][WAVE] Boss not found");
}
// Yer tutucu dalga spawnları
sys_log(0, "[WhiteDragon][WAVE] Spawning wave mobs");
Spawn(6784, 280, 320, 0, true);
Spawn(6785, 320, 280, 0, true);
Spawn(6796, 300, 340, 0, true);
}
void CMap::BuildColdAreas()
{
m_coldCenters.clear();
// 12 soğuk alan merkezi - boss etrafında dairesel dağılım
// Boss merkezi: 134, 207 (Town.txt pozisyonu)
int cx[] = {120, 126, 134, 142, 148, 154, 120, 134, 148, 126, 134, 142};
int cy[] = {194, 194, 194, 194, 194, 194, 207, 207, 207, 220, 220, 220};
for (int i=0;i<12;i++) m_coldCenters.emplace_back(cx[i], cy[i]);
m_coldStacks.clear();
m_coldInside.clear();
sys_log(0, "[WhiteDragon] BuildColdAreas: 12 centers prepared");
}
void CMap::SpawnColdAreas()
{
sys_log(0, "[WhiteDragon] SpawnColdAreas: Spawning 12 cold area markers");
// Soğuk alanlar yavaş yavaş ortaya çıkacak (Wiki: Alastor uyandıktan sonra nüfus eder)
// Şimdilik hepsi doğrudan spawn; ileride event ile yavaş eklenebilir
// Placeholder: görünmez NPC veya özel vnum kullanabiliriz
// Şimdilik sadece koordinat kaydı yeterli; render client tarafında olacak
sys_log(0, "[WhiteDragon] Cold areas registered, no visual spawn yet");
}
void CWD::Initialize()
{
sys_log(0, "[WhiteDragon] CWD::Initialize called");
m_instances.clear();
}
void CWD::Destroy()
{
sys_log(0, "[WhiteDragon] CWD::Destroy called, instances=%lu", m_instances.size());
for (auto &it : m_instances)
{
SECTREE_MANAGER::instance().DestroyPrivateMap(it.first);
it.second->Destroy();
M2_DELETE(it.second);
}
m_instances.clear();
}
void CWD::Remove(long idx)
{
auto it = m_instances.find(idx);
if (it != m_instances.end()) m_instances.erase(it);
}
bool CWD::Access(LPCHARACTER pChar, bool bHard)
{
if (!pChar)
{
sys_log(0, "[WhiteDragon][ACCESS] pChar is NULL");
return false;
}
sys_log(0, "[WhiteDragon][ACCESS] %s requesting %s", pChar->GetName(), bHard ? "HARD" : "EASY");
long base = WD_MAP_BOSS;
long inst = SECTREE_MANAGER::instance().CreatePrivateMap(base);
if (!inst)
{
sys_log(0, "[WhiteDragon][ACCESS] CreatePrivateMap FAILED for base %ld", base);
return false;
}
sys_log(0, "[WhiteDragon][ACCESS] PrivateMap created: %ld", inst);
CMap* pMap = M2_NEW CMap(inst, bHard);
if (!pMap)
{
sys_log(0, "[WhiteDragon][ACCESS] CMap allocation FAILED");
return false;
}
m_instances.insert(std::make_pair(inst, pMap));
sys_log(0, "[WhiteDragon][ACCESS] CMap registered, instances count: %lu", m_instances.size());
PIXEL_POSITION pos;
if (!SECTREE_MANAGER::instance().GetRecallPositionByEmpire(base, 0, pos))
{
sys_log(0, "[WhiteDragon][ACCESS] GetRecallPosition FAILED for base %ld", base);
return true;
}
sys_log(0, "[WhiteDragon][ACCESS] Warp target: %ld,%ld", pos.x, pos.y);
LPPARTY pParty = pChar->GetParty();
if (pParty)
{
struct FMove
{
CMap* p; PIXEL_POSITION pos;
void operator()(LPCHARACTER ch)
{
if (!ch) return;
sys_log(0, "[WhiteDragon][ACCESS] Warping party member: %s", ch->GetName());
ch->SaveExitLocation();
ch->WarpSet(pos.x, pos.y, p->GetMapIndex());
ch->SetSungMaWill();
}
} f{pMap, pos};
pParty->ForEachOnMapMember(f, pChar->GetMapIndex());
sys_log(0, "[WhiteDragon][ACCESS] Party warp complete");
}
else
{
sys_log(0, "[WhiteDragon][ACCESS] Solo entry: %s", pChar->GetName());
pChar->SaveExitLocation();
pChar->WarpSet(pos.x, pos.y, pMap->GetMapIndex());
pChar->SetSungMaWill();
}
sys_log(0, "[WhiteDragon][ACCESS] Access complete, success=true");
return true;
}
void CWD::OnKill(LPCHARACTER pkMonster, LPCHARACTER pKiller)
{
if (!pkMonster || !pKiller) return;
long idx = pKiller->GetMapIndex();
if (idx < 1000) return;
auto it = m_instances.find(idx);
if (it == m_instances.end()) return;
it->second->OnKill(pkMonster, pKiller);
}
void CWD::StartDungeon(LPCHARACTER pkChar)
{
if (!pkChar)
{
sys_log(0, "[WhiteDragon] StartDungeon: pkChar is NULL");
return;
}
long idx = pkChar->GetMapIndex();
sys_log(0, "[WhiteDragon] StartDungeon called by %s at map %ld", pkChar->GetName(), idx);
auto it = m_instances.find(idx);
if (it == m_instances.end())
{
sys_log(0, "[WhiteDragon] StartDungeon: map instance not found for %ld", idx);
return;
}
sys_log(0, "[WhiteDragon] StartDungeon: found instance, calling CMap::StartDungeon");
it->second->StartDungeon(pkChar);
}
}
WhiteDragon.h:
#pragma once
#include "stdafx.h"
#include "char.h"
#include "sectree_manager.h"
#include "party.h"
#include <vector>
#include <unordered_map>
namespace WhiteDragon
{
enum eWhiteDragonConfig
{
WD_MAP_BOSS = 389,
WD_TIME_LIMIT_SECONDS = 1800,
WD_WAVE1 = 1650, // 27:30
WD_WAVE2 = 1500, // 25:00
WD_WAVE3 = 1350, // 22:30
WD_EGG_VNUM = 4030,
WD_BOSS_EASY_VNUM = 6791,
WD_BOSS_HARD_VNUM = 6790,
WD_HEAL_EASY_PCT = 30,
WD_HEAL_HARD_PCT = 50,
WD_COLD_RADIUS = 150, // pixels (approx 1.5 cells)
};
class CMap
{
public:
CMap(long lMapIndex, bool bHard);
~CMap();
void Destroy();
void StartDungeon(LPCHARACTER pkChar);
void OnKill(LPCHARACTER pkMonster, LPCHARACTER pKiller);
void EndDungeonWarp();
void Start();
void OnWave(BYTE wave, bool hard);
void HealBossPercent(int pct);
void BuildColdAreas();
void SpawnColdAreas();
LPCHARACTER Spawn(DWORD dwVnum, int iX, int iY, int iDir, bool bSpawnMotion = false);
void SetDungeonStep(BYTE bStep);
BYTE GetDungeonStep() const { return m_bStep; }
void SetHard(bool b) { m_bHard = b; }
bool IsHard() const { return m_bHard; }
void SetParty(LPPARTY p) { m_pParty = p; }
LPPARTY & GetParty() { return m_pParty; }
void SetMapSectree(LPSECTREE_MAP p) { m_pSectree = p; }
LPSECTREE_MAP & GetMapSectree() { return m_pSectree; }
void SetMapIndex(long i) { m_lMapIndex = i; }
long GetMapIndex() const { return m_lMapIndex; }
void SetEgg(LPCHARACTER egg) { m_pkEgg = egg; }
LPCHARACTER GetEgg() const { return m_pkEgg; }
private:
long m_lMapIndex;
LPSECTREE_MAP m_pSectree;
LPPARTY m_pParty;
LPCHARACTER m_pkEgg;
BYTE m_bStep;
bool m_bHard;
LPEVENT m_evSpawn, m_evLimit, m_evSpike, m_evWave1, m_evWave2, m_evWave3, m_evColdScan, m_evColdTick;
std::vector<std::pair<int,int>> m_coldCenters;
std::unordered_map<DWORD, int> m_coldStacks;
std::unordered_map<DWORD, bool> m_coldInside;
public: // accessors for functors
const std::vector<std::pair<int,int>>& GetColdCenters() const { return m_coldCenters; }
bool IsColdInside(DWORD vid) const { auto it=m_coldInside.find(vid); return it!=m_coldInside.end() && it->second; }
void SetColdInside(DWORD vid, bool v){ m_coldInside[vid]=v; }
bool HasColdStack(DWORD vid) const { return m_coldStacks.find(vid)!=m_coldStacks.end(); }
int GetColdStack(DWORD vid) const { auto it=m_coldStacks.find(vid); return it==m_coldStacks.end()?0:it->second; }
void SetColdStack(DWORD vid, int s){ m_coldStacks[vid]=s; }
void ClearColdStack(DWORD vid){ m_coldStacks.erase(vid); m_coldInside.erase(vid); }
};
class CWD : public singleton<CWD>
{
public:
void Initialize();
void Destroy();
void Remove(long lMapIndex);
bool Access(LPCHARACTER pChar, bool bHard);
void OnKill(LPCHARACTER pkMonster, LPCHARACTER pKiller);
void StartDungeon(LPCHARACTER pkChar);
bool IsWhiteMap(long lMapIndex) const { return lMapIndex >= 1000; }
private:
std::map<long, CMap*> m_instances;
};
}