forked from DFHack/dfhack
-
Notifications
You must be signed in to change notification settings - Fork 0
/
unit.rb
278 lines (245 loc) · 10.9 KB
/
unit.rb
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
module DFHack
class << self
# return an Unit
# with no arg, return currently selected unit in df UI ('v' or 'k' menu)
# with numeric arg, search unit by unit.id
# with an argument that respond to x/y/z (eg cursor), find first unit at this position
def unit_find(what=:selected, y=nil, z=nil)
if what == :selected
return world.units.all.binsearch(df.get_selected_unit_id)
elsif what.kind_of?(Integer)
# search by id
return world.units.all.binsearch(what) if not z
# search by coords
x = what
world.units.all.find { |u| u.pos.x == x and u.pos.y == y and u.pos.z == z }
elsif what.respond_to?(:x) or what.respond_to?(:pos)
world.units.all.find { |u| same_pos?(what, u) }
else
raise "what what?"
end
end
# returns an Array of all units that are current fort citizen (dwarves, on map, not hostile)
def unit_citizens
world.units.active.find_all { |u|
unit_iscitizen(u)
}
end
def unit_testflagcurse(u, flag)
return false if u.curse.rem_tags1.send(flag)
return true if u.curse.add_tags1.send(flag)
return false if u.caste < 0
u.race_tg.caste[u.caste].flags[flag]
end
def unit_isfortmember(u)
# RE from viewscreen_unitlistst ctor
return false if df.gamemode != :DWARF or
u.mood == :Berserk or
unit_testflagcurse(u, :CRAZED) or
unit_testflagcurse(u, :OPPOSED_TO_LIFE) or
u.enemy.undead or
u.flags3.ghostly or
u.flags1.marauder or u.flags1.active_invader or u.flags1.invader_origin or
u.flags1.forest or
u.flags1.merchant or u.flags1.diplomat
return true if u.flags1.tame
return false if u.flags2.underworld or u.flags2.resident or
u.flags2.visitor_uninvited or u.flags2.visitor or
u.civ_id == -1 or
u.civ_id != df.ui.civ_id
true
end
# return the page in viewscreen_unitlist where the unit would appear
def unit_category(u)
return if u.flags1.left or u.flags1.incoming
# return if hostile & unit_invisible(u) (hidden_in_ambush or caged+mapblock.hidden or caged+holder.ambush
return :Dead if u.flags2.killed
return :Dead if u.flags3.ghostly # hostile ?
return if u.flags1.inactive
return :Others if !unit_isfortmember(u)
casteflags = u.race_tg.caste[u.caste].flags if u.caste >= 0
return :Livestock if casteflags and (casteflags[:PET] or casteflags[:PET_EXOTIC])
return :Citizens if unit_testflagcurse(u, :CAN_SPEAK)
:Livestock
# some other stuff with ui.race_id ? (jobs only?)
end
# merchant: df.ui.caravans.find { |cv| cv.entity == u.civ_id }
# diplomat: df.ui.dip_meeting_info.find { |m| m.diplomat_id == u.hist_figure_id or m.diplomat_id2 == u.hist_figure_id }
def unit_nemesis(u)
if ref = u.general_refs.find { |r| r.kind_of?(DFHack::GeneralRefIsNemesisst) }
ref.nemesis_tg
end
end
# return the subcategory for :Others (from vs_unitlist)
def unit_other_category(u)
# comment is actual code returned by the df function
return :Berserk if u.mood == :Berserk # 5
return :Berserk if unit_testflagcurse(u, :CRAZED) # 14
return :Undead if unit_testflagcurse(u, :OPPOSED_TO_LIFE) # 1
return :Undead if u.flags3.ghostly # 15
if df.gamemode == :ADVENTURE
return :Hostile if u.civ_id == -1 # 2
if u.animal.population.region_x == -1
return :Wild if u.flags2.roaming_wilderness_population_source_not_a_map_feature # 0
else
return :Hostile if u.flags2.important_historical_figure and n = unit_nemesis(u) and n.flags[:ACTIVE_ADVENTURER] # 2
end
return :Hostile if u.flags2.resident # 3
return :Hostile # 4
end
return :Invader if u.flags1.active_invader or u.flags1.invader_origin # 6
return :Friendly if u.flags1.forest or u.flags1.merchant or u.flags1.diplomat # 8
return :Hostile if u.flags1.tame # 7
if u.civ_id != -1
return :Unsure if u.civ_id != df.ui.civ_id or u.flags1.resident or u.flags1.visitor or u.flags1.visitor_uninvited # 10
return :Hostile # 7
elsif u.animal.population.region_x == -1
return :Friendly if u.flags2.visitor # 8
return :Uninvited if u.flags2.visitor_uninvited # 12
return :Underworld if r = u.race_tg and r.underground_layer_min == 5 # 9
return :Resident if u.flags2.resident # 13
return :Friendly # 8
else
return :Friendly if u.flags2.visitor # 8
return :Underworld if r = u.race_tg and r.underground_layer_min == 5 # 9
return :Wild if u.animal.population.feature_idx == -1 and u.animal.population.cave_id == -1 # 0
return :Wild # 11
end
end
def unit_iscitizen(u)
unit_category(u) == :Citizens
end
def unit_hostiles
world.units.active.find_all { |u|
unit_ishostile(u)
}
end
# returns if an unit is openly hostile
# does not include ghosts / wildlife
def unit_ishostile(u)
# return true if u.flags3.ghostly and not u.flags1.inactive
return unless unit_category(u) == :Others
case unit_other_category(u)
when :Berserk, :Undead, :Hostile, :Invader, :Underworld
# XXX :Resident, :Uninvited?
true
when :Unsure
# from df code, with removed duplicate checks already in other_category
return true if u.enemy.undead or u.flags3.ghostly or u.flags1.marauder
return false if u.flags1.forest or u.flags1.merchant or u.flags1.diplomat or u.flags2.visitor
return true if u.flags1.tame or u.flags2.underworld
if histfig = u.hist_figure_tg
group = df.ui.group_tg
case unit_checkdiplomacy_hf_ent(histfig, group)
when 4, 5
true
end
elsif diplo = u.civ_tg.unknown1b.diplomacy.binsearch(df.ui.group_id, :group_id)
diplo.relation != 1 and diplo.relation != 5
else
u.animal.population.region_x != -1 or u.flags2.resident or u.flags2.visitor_uninvited
end
end
end
def unit_checkdiplomacy_hf_ent(histfig, group)
var_3d = var_3e = var_45 = var_46 = var_47 = var_48 = var_49 = nil
var_3d = 1 if group.type == :Outcast or group.type == :NomadicGroup or
(group.type == :Civilization and group.entity_raw.flags[:LOCAL_BANDITRY])
histfig.entity_links.each { |link|
if link.entity_id == group.id
case link.getType
when :MEMBER, :MERCENARY, :SLAVE, :PRISONER, :POSITION, :HERO
var_47 = 1
when :FORMER_MEMBER, :FORMER_MERCENARY, :FORMER_SLAVE, :FORMER_PRISONER
var_48 = 1
when :ENEMY
var_49 = 1
when :CRIMINAL
var_45 = 1
end
else
case link.getType
when :MEMBER, :MERCENARY, :SLAVE
if link_entity = link.entity_tg
diplo = group.unknown1b.diplomacy.binsearch(link.entity_id, :group_id)
case diplo.relation
when 0, 3, 4
var_48 = 1
when 1, 5
var_46 = 1
end
var_3e = 1 if link_entity.type == :Outcast or link_entity.type == :NomadicGroup or
(link_entity.type == :Civilization and link_entity.entity_raw.flags[:LOCAL_BANDITRY])
end
end
end
}
if var_49
4
elsif var_46
5
elsif !var_47 and group.resources.ethic[:KILL_NEUTRAL] == 16
4
elsif df.gamemode == :ADVENTURE and !var_47 and (var_3e or !var_3d)
4
elsif var_45
3
elsif var_47
2
elsif var_48
1
else
0
end
end
# list workers (citizen, not crazy / child / inmood / noble)
def unit_workers
world.units.active.find_all { |u|
unit_isworker(u)
}
end
def unit_isworker(u)
unit_iscitizen(u) and
u.race == df.ui.race_id and
u.mood == :None and
u.profession != :CHILD and
u.profession != :BABY and
# TODO MENIAL_WORK_EXEMPTION_SPOUSE
!unit_entitypositions(u).find { |pos| pos.flags[:MENIAL_WORK_EXEMPTION] }
end
# list currently idle workers
def unit_idlers
world.units.active.find_all { |u|
unit_isidler(u)
}
end
def unit_isidler(u)
unit_isworker(u) and
# current_job includes eat/drink/sleep/pickupequip
!u.job.current_job and
# filter 'attend meeting'
not u.specific_refs.find { |s| s.type == :ACTIVITY } and
# filter soldiers (TODO check schedule)
u.military.squad_id == -1 and
# filter incoming migrants
not u.status.misc_traits.find { |t| t.id == :Migrant }
end
def unit_entitypositions(unit)
list = []
return list if not histfig = unit.hist_figure_tg
histfig.entity_links.each { |el|
next if el._rtti_classname != :histfig_entity_link_positionst
next if not ent = el.entity_tg
next if not pa = ent.positions.assignments.binsearch(el.assignment_id)
next if not pos = ent.positions.own.binsearch(pa.position_id)
list << pos
}
list
end
end
class LanguageName
def to_s(english=false)
df.translate_name(self, english)
end
end
end