Official Alastor Dungeon

  • Konuyu açan Konuyu açan Raviel
  • Açılış Tarihi Açılış Tarihi
  • Yanıt Yanıt 4
  • Gösterim Gösterim 278

Raviel

Premium Üye
Premium Üye
MT Üye
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:



WhiteDragon.cpp:
Genişlet Daralt Kopyala
#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:
Genişlet Daralt Kopyala
#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;
    };
}
 
Hemen bir güncelleme verelim. Benim için büyük devler için küçük bir adım olsada bütün günümü bossların veya oyunda efekt sisteminin tam olarak nasıl çalıştığını yapay zekaya anlatmakla ve Alastor'un attığı skiller dışında gelen 12 adet Ölümcül Alastor Soğuğunu yapmakla geçirdim. Gerçekten çok sinir bozucuydu bunu yapmak ama sonunda başardım diyebilirim.

Sizin için Matematikte Kümeler konusunu Alastor üzerinden işliycez.

1760566597023.webp


Bilmeyenler için bu nedir?

1760566628720.webp


Şu anda etkileri aktif olarak çalışıyor. Üzerinde durulduğunda anında 15.000 HP siliyor ve 5 Stack'e kadar birikebiliyor. Her 5 saniyede bir stack ekleniyor. Şu anda bu stack süresi her kürede farklı şekilde çalışıyor ancak bunuda toplamda 5 olacak şekilde düzeltip bir adette affect simgesiyle belirteceğim. Üzerinde durduğunuzda Hp'nizin %5'ine eşit miktarda hasarda veriyor. Bugün bütün gün bununla uğraştığım için yarın hem bunların düzenlemesi hemde Ice Spikes yani:
1760566777718.webp


Bununda efektini tamamladıktan sonra Alastor'un Special Skilleri ve animasyonlarına geçeceğim. Uzun ve yorucu bir süreç bizi bekliyor. Takip eden ve beğenen herkese destekleri için teşekkür ederim. Bu yolda bana çok güzel motivasyon oluyorsunuz :)
 
Herkese selamlar tekrardan,
Neredeyse tamamlamak üzereyim artık Dungeon tarafını ve skill tarafını. Alastor için Buz dikenleri, Ölümcül soğuk gibi skiller ve efektleri eklendi. Timing ayarlamaları yapıldı. Alastor'un 1. ve 2. skilli eklendi. Timing ayarlamaları yapıldı. Şu anda üzerinde durduğum kısım ise efektlerin olduğu zamanda doğru şekilde doğru yerde karakterlerin hasar yemeleri. Zaman zaman bir skill animasyonu aktifken arka tarafta sanki diğer bir skill animasyonuda devreye giriyor ve görünmez hasar yiyorum. Yani skill animasyonu olmadan karakter hasar alıyor. Bunu çözmeye çalışıyorum onun dışında neredeyse bitti sayılır. Bunu çözdükten sonra 3. skillide ekleyerek dungeon tarafında eksik kalan modeller, mob sabitlemeleri ve dungeon kayıt, ödül alma gibi mekanikleri tamamlıycam.

Şu an için test amaçlı olarak grupsuz şekilde giriliyor.

Yerde oluşan yuvarlaklar anında 15.000 HP silmesi gerekiyor normalde ancak henüz tam doğru çalışmıyor onun üzerinde çalışıyorum neden kaynaklandığını tam bilmiyorum. Üzerinize bir DoT bırakıyor yani bir affect işliyor ve bu her 5 saniyede bir toplam can değerinizin %5'ine eşit miktar hasar vuruyor. Buz dikenleri(Videoda oluşan alan hasarı vuran skiller) içinde kalan kişinin toplam can değerinin %50'sine eşit miktarda hasar vuruyor.

Alastor'un Special Attack 1 skili (Etrafta oluşan küçük yuvarlak daireler) daire içerisindeyseniz eğer 25.000 Hasar veriyor. Alastor'un skilleri tamamen ICE_Damage üzerinden işliyor. Başka türlüde yapabilirim fikri olan veya benzer sistemleri inceleyen arkadaşların fikirleri ve önerilerini bekliyorum.

Alastor'un Special Attack 2 skili yani kapanma skilli. 0-4.26 saniye arasında gelen bütün hasarları topluyor ve 4.26 saniyede gerçekleştirdiği patlamada yediği hasarın %100'ü kadar yansıtma yapıyor. Henüz daha yukarıdakiyle aynı senkron problemi olabilir tam bilemiyorum ama doğru şekilde hasar topladığını veya yansıttığını düşünmüyorum. Bunun için mevcut koddan farklı olarak bir yansıtma sistemi kullanıyorum. Yani mevcut kodda ki Reflect'i kullanmıyorum. Belkide onu kullanıp tek seferde splashdamage ile AoE hasarı oluşturmam gerekiyor bilemiyorum. Şu an videoda mapte ki tüm parti üyelerine aynı miktarda hasar vuruyor ancak bunu AoE hasarını çeviricem ve yakınında ki tüm herkese vuracak.

 
Son düzenleme:
Herkese tekrardan selamlar,

An itibariyle Alastor'un %90'lık kısmı bitti. Son videodan beri neler yaptık?

1- Alastor'un Special Attack 1-2-3'ü eklendi.
2- Vurulan hasarlar ICE_DAMAGE yerine DAMAGE_SPECIAL'a çevrildi.
3- Kapanma skilli yani special attack2 için mevcut oyunda ki REFLECTION mantığı kullanılarak önce toplama animasyon patlama anında 2000px alanda hasar vermesi için düzenlendi.
4- Bütün Dungeon'da geçerli olan animasyon ve efektlerle birlikte skillerin hasarları Official'a benzer şekilde ayarlandı.
5- SungMa statları Dungeon'a özel hem kolay hem zor için ayarlandı.

Yani sistem neredeyse tamamlandı. Bunların yaparken en sinir bozucu şeylerden biri Metin2'nin client ve server tarafında ki konum eşleşmezliği oldu. Bunuda bu sistemi bitirdikten sonra baştan sona ele alıp farklı bir forumda ki önerileri kullanarak değiştirmeyi düşünüyorum. Hatta bu sistem öyle kanser ki sizi bir çok açıdan engelliyor. Yeni mekaniklerde koymanız zorlaşıyor.

Tabi ki rastladığım hatalarda oldu bu süreçte
1- Skilleri Alastor'da belirli bir saniye aralığında spamlatıyorum buda animasyonların karışmasına ve görünmez hasar yememe yol açıyordu.
2- Boss skill atarken animasyon esnasında görünmez şekilde yanıma yaklaşıp bana düz şekilde vurabiliyordu.
3- Verilen hasarların oyunun genel efsunları gereğince bloklanması sebebiyle doğru şekilde hasar vurmuyordu.
4- Kapanma skilli maalesef oyunda ki MS'ten kaynaklı olarak tam vurduğunuz anda işlememe durumu olabiliyordu.
5- Boss'un skill animasyonunda durmasını ayarladıktan sonra bir daha Victim seçmiyor ve hareket etmiyordu.

Bunun gibi bir çok hatayla karşılaştım. Merak edenler için aşağıya WhiteDragon.cpp ve WhiteDragon.h dosyalarınıda bıraktım. Tamamen hepsi sıfırdan yazıldı. Yani mevcut Rakancito'nun dosyalarından bazı şeyleri örnek aldım sadece.

Merak edenler için:

Kalan yapılması gereken şeyler ise;
1 - Quest'in tamamlanması ve çıkan oyuncuların aynı Official'da ki gibi NPC'de ödüllerini alması.
2 - Alastor'un official'da ki gibi belirli HP değerinin altına düşünce hasarının artması.
3 - Alastor'un belirli sürelerde spawnladığı mob gruplarının ayarlanması
4 - HP Barının düzeltilmesi ve can değerlerinin Official'da ki gibi ayarlanması. (long long şeklinde denemelerim oldu ancak çok kısa bir denemem oldu o sebeple üzerinde çok durmadım.)
5 - UI tarafında sağ tarafta süre sayacı ve Buff/Debuff etkilerinin yazılması.
6 - Eksik kalan modellerin (İçeride ki yumurta vb.) tamamlanması.

Buda sistemin videosu(Sonuna doğru bir ses patlaması olabilir dikkat edin :) )


 
Son düzenleme:
Selamlar,

Quest yapısı neredeyse tamamlandı. %90 Official tarzda yapıldı.
Eğer bir oyuncu dungeon sonrası ödülünü almayın unutursa görevlere ödülünü alması için bir quest ekleniyor. Mevcut alması gereken bir ödül varsa NPC'nin üstüne ok işareti çıkıp yanıp sönüyor. Videoyu izleyince daha net anlayacaksınız.

1761009026245.webp

1761009068659.webp

Kalan eksik kısımlar ise şu şekilde:

- Alastor'un wave şeklinde spawnladığı moblar eklenecek.
- Yumurta modeli ve yumurtaya tıklayıp dungeon başlatıldığında olan alastor'un spawn efekti eklenecek.

Yeni Video:


 
Geri
Üst