aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Metin2

Metin2 Quest Timer: Usage and Examples

The Metin2 quest timer is one of the most powerful — and most often misused — parts of the quest system: it lets you fire an event not instantly, but after a set amount of time. Whether you want a countdown quest, a warning that repeats at intervals, a boss respawn delay, or a cooldown like "you can claim this reward once every 24 hours," timers sit at the heart of all of it. In this article we'll walk through the two main timer mechanisms in the Metin2 quest engine — the player-bound q.start_timer and the global server_timer — step by step, with real, working examples.

What a quest timer is and how it works

The quest engine is event-driven: when ... begin ... end blocks react to specific events (chat, kill, login). A timer adds a "delay" to that logic. When you start a timer the engine registers it, and when it expires it raises a special event — which you again catch with a when block. There are two kinds:

  • Player-bound timer (q.start_timer): the timer belongs to a specific character. When it fires, the block runs in that player's context, so you can call pc.* functions directly.
  • Global server timer (server_timer): the timer belongs to the server, not a player. It's ideal for work that isn't tied to anyone, like boss respawns or map events.

A player-bound timer with q.start_timer

This is the most commonly needed scenario: a player talks to an NPC, and X seconds later something happens. You start it with q.start_timer("name", seconds) and catch the result in a when name.timer block.

quest gift_box begin
    state start begin
        when 20095.chat."Open the box" begin
            say_title("Merchant:")
            say("The box will open in 10 seconds, wait...")
            q.start_timer("open_box", 10)
        end

        when open_box.timer begin
            say("The box opened! Here is your reward.")
            pc.give_item2(27003, 1)   -- example item vnum
            pc.give_exp2(5000)
        end
    end
end

The key point: the open_box.timer block already runs in the correct player's context, because we started the timer for that player. If you call q.start_timer again with a name that already exists, the old one is replaced by the new one, which conveniently prevents accidental double firing.

Global delayed events with server_timer

Suppose you want a boss to respawn one hour after it dies. That has nothing to do with a specific player, so you use server_timer. The signature is server_timer("name", seconds, arg). You can pass a VID or a number as the third parameter and read it back with get_server_timer_arg() when the timer fires.

quest boss_respawn begin
    state start begin
        when 6091.kill begin
            -- Boss died, respawn it after 1 hour
            local map_index = pc.get_map_index()
            server_timer("boss_back", 60 * 60, map_index)
            notice_all("The boss has fallen! It will return in 1 hour.")
        end

        when boss_back.server_timer begin
            local map_index = get_server_timer_arg()
            regen_in_map(map_index, "data/boss_regen.txt")
            notice_all("The boss has respawned!")
        end
    end
end

Inside a server_timer block there is no default player context. If you need to act on a specific player, carry their PID through the argument and use the q.begin_other_pc_block(pid) ... q.end_other_pc_block() pattern.

The cooldown pattern

Timers are for things that will fire "soon," but a persistent cooldown like "claim this reward once every 24 hours" must survive even a server restart. The right way to do this is not a timer, but writing a timestamp into a player flag. get_global_time() returns the current server time in seconds; you store it with pc.setqf and compare later.

quest daily_reward begin
    state start begin
        when 20095.chat."Daily reward" begin
            local now = get_global_time()
            local last = pc.getqf("last_reward")
            local cooldown = 24 * 60 * 60   -- 24 hours

            if now - last < cooldown then
                local remaining = cooldown - (now - last)
                say("Wait " .. math.floor(remaining / 3600) .. " more hours for the next reward.")
                return
            end

            pc.setqf("last_reward", now)
            pc.give_item2(50300, 1)
            say("Your daily reward is ready!")
        end
    end
end

The difference is critical: q.start_timer is lost when the server restarts, but the timestamp written to a flag stays in the database. For persistent cooldowns, always prefer this pattern.

Repeating timers and cancelling them

For a task that repeats at intervals (say, a warning every 30 seconds), you re-arm the timer inside its own block. When you want to stop, you break the loop with a quest flag — because if you simply don't restart it, the loop stops on its own.

quest counter begin
    state start begin
        when login begin
            pc.setqf("round", 0)
            q.start_timer("tick", 30)
        end

        when tick.timer begin
            local round = pc.getqf("round") + 1
            pc.setqf("round", round)
            chat("Round: " .. round)

            if round < 5 then
                q.start_timer("tick", 30)   -- re-arm = continue
            else
                chat("Counter finished.")    -- don't re-arm = stop
            end
        end
    end
end

If you need to cancel a player-bound timer early, q.clear_timer() clears the running timers. For a global timer, use q.clear_server_timer("name", arg).

Common mistakes and tips

  • Timer name doesn't match the when block: if you started q.start_timer("open_box", 10), the block must be when open_box.timer. The names must match exactly.
  • Using a timer for a persistent cooldown: the timer is wiped on a server restart. For long, persistent durations use the get_global_time() + flag pattern.
  • Calling pc.* inside server_timer: there's no player in the global context; carry the PID via the argument and enter a q.begin_other_pc_block block.
  • Very short intervals: avoid sub-second work; the quest engine runs at second resolution, and timers that fire too often hurt performance.
  • Check syserr: if a timer isn't firing as expected, look at syserr.txt first — Lua errors usually show up there.

Frequently Asked Questions

What's the difference between q.start_timer and server_timer?

q.start_timer belongs to a specific player and the block runs in that player's context; it's dropped if the character logs out. server_timer belongs to the server, is tied to no one, and is used for global events like boss respawns.

What happens to timers if the server restarts?

In-memory timers are lost. That's why you should manage waits that last hours or days not with a timer, but by writing get_global_time() into a player flag; the flag persists in the database.

Can I run several timers at once?

Yes, just give each a different name. Starting a new timer with the same name cancels the old one and arms the new, which is sometimes used deliberately as a "reset" mechanism.

Stuck on the quest system? If you need help with Metin2 quest development — timer-based quests, dungeon logic or custom event systems — get in touch and let's solve your project together.

Bu kategorideki tüm yazılar →

Devamı için