summaryrefslogtreecommitdiff
path: root/data/scripts/libtmw.lua
blob: bb72479980238979391e11232f9d456fd7161108 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
------------------
-- Support code --
------------------

-- Table that associates to each NPC pointer the handler function that is
-- called when a player starts talking to an NPC.
local npcs = {}

-- Table that associates to each Character pointer its state with respect to
-- NPCs (only one at a time). A state is an array with four fields:
-- . 1: pointer of the NPC the player is currently talking to.
-- . 2: coroutine running the NPC handler.
-- . 3: next query the NPC expects from a player (1 = next, 2 = choice).
-- . 4: countdown (in minutes) before the state is deleted.
local states = {}

-- Array containing the function registered by atinit.
local init_fun = {}

-- Tick timer used during update to clean obsolete states.
local timer

-- Creates an NPC and associates the given handler.
-- Note: Cannot be called until map initialization has started.
function create_npc(id, x, y, handler)
  local npc = tmw.npc_create(id, x, y)
  npcs[npc] = handler
end

-- Sends an npc message to a player.
-- Note: Does not wait for the player to acknowledge the message.
function do_message(npc, ch, msg)
  -- Wait for the arrival of a pending acknowledgment, if any.
  coroutine.yield(0)
  tmw.npc_message(npc, ch, msg)
  -- An acknowledgment is pending, but do not wait for its arrival.
  coroutine.yield(1)
end

-- Sends an NPC question to a player and waits for its answer.
function do_choice(npc, ch, ...)
  -- Wait for the arrival of a pending acknowledgment, if any.
  coroutine.yield(0)
  tmw.npc_choice(npc, ch, ...)
  -- Wait for player choice.
  return coroutine.yield(2)
end

-- Processes as much of an NPC handler as possible.
local function process_npc(co, ...)
  -- First, resume with the arguments the coroutine was waiting for.
  local b, v = coroutine.resume(co, ...)
  if not b or not v then
    return
  end
  if v == 2 then
    return 2
  end
  -- Then, keep resuming until the coroutine expects the result of a choice
  -- or an acknowledgment to a message.
  local pending = (v == 1)
  while true do
    b, v = coroutine.resume(co)
    if not b or not v then
      return
    end
    if v == 2 then
      return 2
    end
    if pending then
      return 1
    end
    if v == 1 then
      pending = true
    end
  end
end

-- Called by the game whenever a player starts talking to an NPC.
-- Creates a coroutine based on the registered NPC handler.
function npc_start(npc, ch)
  states[ch] = nil
  local h = npcs[npc]
  if not h then return end
  local co = coroutine.create(h)
  local v = process_npc(co, npc, ch)
  if v then
    states[ch] = {npc, co, v, 5}
    if not timer then
      timer = 600
    end
  end
end

-- Called by the game whenever a player keeps talking to an NPC.
-- Checks that the NPC expects it, and processes the respective coroutine.
function npc_next(npc, ch)
  local w = states[ch]
  if w and w[1] == npc and w[3] == 1 then
    local v = process_npc(w[2])
    if v then
      w[3] = v
      w[4] = 5
      return
    end
  end
  states[ch] = nil
end

-- Called by the game whenever a player selects a particular reply.
-- Checks that the NPC expects it, and processes the respective coroutine.
function npc_choose(npc, ch, u)
  local w = states[ch]
  if w and w[1] == npc and w[3] == 2 then
    local v = process_npc(w[2], u)
    if v then
      w[3] = v
      w[4] = 5
      return
    end
  end
  states[ch] = nil
end

-- Called by the game every tick for each NPC.
function npc_update(npc)
end

-- Called by the game every tick.
-- Cleans obsolete connections.
function update()
  -- Run every minute only, in order not to overload the server.
  if not timer then return end
  timer = timer - 1
  if timer ~= 0 then return end
  -- Free connections that have been inactive for 3-4 minutes.
  for k, w in pairs(states) do
    local t = w[4] - 1
    if t == 0 then
      states[k] = nil
    else
      w[4] = t
    end
  end
  -- Restart timer if there are still some pending states.
  if next(states) then
    timer = 600
  else
    timer = nil
  end
end

-- Registers a function so that is is executed during map initialization.
function atinit(f)
  init_fun[#init_fun + 1] = f
end

-- Called by the game for creating NPCs embedded into maps.
-- Delays the creation until map initialization is performed.
-- Note: Assumes that the "npc_handler" global field contains the NPC handler.
function create_npc_delayed(id, x, y)
  -- Bind the name to a local variable first, as it will be reused.
  local h = npc_handler
  atinit(function() create_npc(id, x, y, h) end)
  npc_handler = nil
end

-- Called during map initialization.
-- Executes all the functions registered by atinit.
function initialize()
  for i,f in ipairs(init_fun) do
    f()
  end
  init_fun = nil
end

-- Below are some convenience methods added to the engine API

tmw.chr_money_change = function(ch, amount)
  return tmw.chr_inv_change(ch, 0, amount)
end

tmw.chr_money = function(ch)
  return tmw.chr_inv_count(ch, 0)
end