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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
//! Information about units, ablities, upgrades, buffs and effects provided by API stored here.
#![allow(missing_docs)]

use crate::{
	ids::{AbilityId, BuffId, EffectId, UnitTypeId, UpgradeId},
	player::Race,
	FromProto, TryFromProto,
};
use num_traits::FromPrimitive;
use rustc_hash::FxHashMap;
use sc2_proto::{
	data::{
		AbilityData as ProtoAbilityData, AbilityData_Target, Attribute as ProtoAttribute,
		BuffData as ProtoBuffData, EffectData as ProtoEffectData, UnitTypeData as ProtoUnitTypeData,
		UpgradeData as ProtoUpgradeData, Weapon as ProtoWeapon, Weapon_TargetType,
	},
	sc2api::ResponseData,
};

/// All the data about different ids stored here.
/// Can be accessed through [`game_data`](crate::bot::Bot::game_data) field.
#[derive(Default, Clone)]
pub struct GameData {
	/// Information about abilities mapped to `AbilityId`s.
	pub abilities: FxHashMap<AbilityId, AbilityData>,
	/// Information about units mapped to `UnitTypeId`s.
	pub units: FxHashMap<UnitTypeId, UnitTypeData>,
	/// Information about upgrades mapped to `UpgradeId`s.
	pub upgrades: FxHashMap<UpgradeId, UpgradeData>,
	/// Information about buffs mapped to `BuffId`s.
	pub buffs: FxHashMap<BuffId, BuffData>,
	/// Information about effects mapped to `EffectId`s.
	pub effects: FxHashMap<EffectId, EffectData>,
}
impl FromProto<ResponseData> for GameData {
	fn from_proto(data: ResponseData) -> Self {
		Self {
			abilities: data
				.get_abilities()
				.iter()
				.filter_map(|a| AbilityData::try_from_proto(a).map(|data| (data.id, data)))
				.collect(),
			units: data
				.get_units()
				.iter()
				.filter_map(|u| UnitTypeData::try_from_proto(u).map(|data| (data.id, data)))
				.collect(),
			upgrades: data
				.get_upgrades()
				.iter()
				.filter_map(|u| UpgradeData::try_from_proto(u).map(|data| (data.id, data)))
				.collect(),
			buffs: data
				.get_buffs()
				.iter()
				.filter_map(|b| BuffData::try_from_proto(b).map(|data| (data.id, data)))
				.collect(),
			effects: data
				.get_effects()
				.iter()
				.filter_map(|e| EffectData::try_from_proto(e).map(|data| (data.id, data)))
				.collect(),
		}
	}
}

/// Cost of an item (`UnitTypeId` or `UpgradeId`) in resources, supply and time.
#[derive(Debug, Default)]
pub struct Cost {
	pub minerals: u32,
	pub vespene: u32,
	pub supply: f32,
	pub time: f32,
}

/// Possible target of ability, needed when giving commands to units.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum AbilityTarget {
	None,
	Point,
	Unit,
	PointOrUnit,
	PointOrNone,
}
impl FromProto<AbilityData_Target> for AbilityTarget {
	fn from_proto(target: AbilityData_Target) -> Self {
		match target {
			AbilityData_Target::None => AbilityTarget::None,
			AbilityData_Target::Point => AbilityTarget::Point,
			AbilityData_Target::Unit => AbilityTarget::Unit,
			AbilityData_Target::PointOrUnit => AbilityTarget::PointOrUnit,
			AbilityData_Target::PointOrNone => AbilityTarget::PointOrNone,
		}
	}
}

/// Differents attributes of units.
#[variant_checkers]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Attribute {
	Light,
	Armored,
	Biological,
	Mechanical,
	Robotic,
	Psionic,
	Massive,
	Structure,
	Hover,
	Heroic,
	Summoned,
}
impl FromProto<ProtoAttribute> for Attribute {
	fn from_proto(attribute: ProtoAttribute) -> Self {
		match attribute {
			ProtoAttribute::Light => Attribute::Light,
			ProtoAttribute::Armored => Attribute::Armored,
			ProtoAttribute::Biological => Attribute::Biological,
			ProtoAttribute::Mechanical => Attribute::Mechanical,
			ProtoAttribute::Robotic => Attribute::Robotic,
			ProtoAttribute::Psionic => Attribute::Psionic,
			ProtoAttribute::Massive => Attribute::Massive,
			ProtoAttribute::Structure => Attribute::Structure,
			ProtoAttribute::Hover => Attribute::Hover,
			ProtoAttribute::Heroic => Attribute::Heroic,
			ProtoAttribute::Summoned => Attribute::Summoned,
		}
	}
}

/// Possible target of unit's weapon.
#[variant_checkers]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum TargetType {
	Ground,
	Air,
	Any,
}
impl FromProto<Weapon_TargetType> for TargetType {
	fn from_proto(target_type: Weapon_TargetType) -> Self {
		match target_type {
			Weapon_TargetType::Ground => TargetType::Ground,
			Weapon_TargetType::Air => TargetType::Air,
			Weapon_TargetType::Any => TargetType::Any,
		}
	}
}

/// Weapon's characteristic.
#[derive(Clone)]
pub struct Weapon {
	/// Possible targets.
	pub target: TargetType,
	/// Usual damage.
	pub damage: u32,
	/// Addidional damage vs units with specific attribute.
	pub damage_bonus: Vec<(Attribute, u32)>,
	/// Number of attacks per use.
	pub attacks: u32,
	/// Maximum range.
	pub range: f32,
	/// Cooldown (in seconds * game speed).
	pub speed: f32,
}
impl FromProto<&ProtoWeapon> for Weapon {
	fn from_proto(weapon: &ProtoWeapon) -> Self {
		Self {
			target: TargetType::from_proto(weapon.get_field_type()),
			damage: weapon.get_damage() as u32,
			damage_bonus: weapon
				.get_damage_bonus()
				.iter()
				.map(|db| (Attribute::from_proto(db.get_attribute()), db.get_bonus() as u32))
				.collect(),
			attacks: weapon.get_attacks(),
			range: weapon.get_range(),
			speed: weapon.get_speed(),
		}
	}
}

/// Information about specific ability.
#[derive(Clone)]
pub struct AbilityData {
	pub id: AbilityId,
	pub link_name: String,
	pub link_index: u32,
	pub button_name: Option<String>,
	pub friendly_name: Option<String>,
	pub hotkey: Option<String>,
	pub remaps_to_ability_id: Option<AbilityId>,
	/// Ability is available in current game version.
	pub available: bool,
	/// Possible target of ability, needed when giving commands to units.
	pub target: AbilityTarget,
	/// Ability can be used on minimap.
	pub allow_minimap: bool,
	/// Ability can be autocasted.
	pub allow_autocast: bool,
	/// Ability is used to construct a building.
	pub is_building: bool,
	/// Half of the building size.
	pub footprint_radius: Option<f32>,
	pub is_instant_placement: bool,
	/// Maximum range to target of the ability.
	pub cast_range: Option<f32>,
}
impl TryFromProto<&ProtoAbilityData> for AbilityData {
	fn try_from_proto(a: &ProtoAbilityData) -> Option<Self> {
		Some(Self {
			id: AbilityId::from_u32(a.get_ability_id())?,
			link_name: a.get_link_name().to_string(),
			link_index: a.get_link_index(),
			button_name: a.button_name.as_ref().cloned(),
			friendly_name: a.friendly_name.as_ref().cloned(),
			hotkey: a.hotkey.as_ref().cloned(),
			remaps_to_ability_id: a.remaps_to_ability_id.and_then(AbilityId::from_u32),
			available: a.get_available(),
			target: AbilityTarget::from_proto(a.get_target()),
			allow_minimap: a.get_allow_minimap(),
			allow_autocast: a.get_allow_autocast(),
			is_building: a.get_is_building(),
			footprint_radius: a.footprint_radius,
			is_instant_placement: a.get_is_instant_placement(),
			cast_range: a.cast_range,
		})
	}
}

/// Information about specific unit type.
#[derive(Clone)]
pub struct UnitTypeData {
	pub id: UnitTypeId,
	pub name: String,
	/// Unit is available in current game version.
	pub available: bool,
	/// Space usage in transports and bunkers.
	pub cargo_size: u32,
	pub mineral_cost: u32,
	pub vespene_cost: u32,
	pub food_required: f32,
	pub food_provided: f32,
	/// Ability used to produce unit or `None` if unit can't be produced.
	pub ability: Option<AbilityId>,
	/// Race of unit.
	pub race: Race,
	pub build_time: f32,
	/// Unit contains vespene (i.e. is vespene geyser).
	pub has_vespene: bool,
	/// Unit contains minerals (i.e. is mineral field).
	pub has_minerals: bool,
	pub sight_range: f32,
	pub tech_alias: Vec<UnitTypeId>,
	pub unit_alias: Option<UnitTypeId>,
	pub tech_requirement: Option<UnitTypeId>,
	pub require_attached: bool,
	pub attributes: Vec<Attribute>,
	pub movement_speed: f32,
	pub armor: i32,
	pub weapons: Vec<Weapon>,
}
impl UnitTypeData {
	pub fn cost(&self) -> Cost {
		Cost {
			minerals: self.mineral_cost,
			vespene: self.vespene_cost,
			supply: self.food_required,
			time: self.build_time,
		}
	}
}
impl TryFromProto<&ProtoUnitTypeData> for UnitTypeData {
	fn try_from_proto(u: &ProtoUnitTypeData) -> Option<Self> {
		Some(Self {
			id: UnitTypeId::from_u32(u.get_unit_id())?,
			name: u.get_name().to_string(),
			available: u.get_available(),
			cargo_size: u.get_cargo_size(),
			mineral_cost: u.get_mineral_cost(),
			vespene_cost: u.get_vespene_cost(),
			food_required: u.get_food_required(),
			food_provided: u.get_food_provided(),
			ability: u.ability_id.and_then(AbilityId::from_u32),
			race: Race::from_proto(u.get_race()),
			build_time: u.get_build_time(),
			has_vespene: u.get_has_vespene(),
			has_minerals: u.get_has_minerals(),
			sight_range: u.get_sight_range(),
			tech_alias: u
				.get_tech_alias()
				.iter()
				.filter_map(|a| UnitTypeId::from_u32(*a))
				.collect(),
			unit_alias: u.unit_alias.and_then(UnitTypeId::from_u32),
			tech_requirement: u.tech_requirement.and_then(UnitTypeId::from_u32),
			require_attached: u.get_require_attached(),
			attributes: u
				.get_attributes()
				.iter()
				.map(|&a| Attribute::from_proto(a))
				.collect(),
			movement_speed: u.get_movement_speed(),
			armor: u.get_armor() as i32,
			weapons: u.get_weapons().iter().map(|w| Weapon::from_proto(w)).collect(),
		})
	}
}

/// Information about specific upgrade.
#[derive(Clone)]
pub struct UpgradeData {
	pub id: UpgradeId,
	/// Ability used to research the upgrade.
	pub ability: AbilityId,
	pub name: String,
	pub mineral_cost: u32,
	pub vespene_cost: u32,
	pub research_time: f32,
}
impl UpgradeData {
	pub fn cost(&self) -> Cost {
		Cost {
			minerals: self.mineral_cost,
			vespene: self.vespene_cost,
			supply: 0.0,
			time: self.research_time,
		}
	}
}
impl TryFromProto<&ProtoUpgradeData> for UpgradeData {
	fn try_from_proto(u: &ProtoUpgradeData) -> Option<Self> {
		Some(Self {
			id: UpgradeId::from_u32(u.get_upgrade_id())?,
			ability: AbilityId::from_u32(u.get_ability_id())?,
			name: u.get_name().to_string(),
			mineral_cost: u.get_mineral_cost(),
			vespene_cost: u.get_vespene_cost(),
			research_time: u.get_research_time(),
		})
	}
}

/// Information about specific buff.
#[derive(Clone)]
pub struct BuffData {
	pub id: BuffId,
	pub name: String,
}
impl TryFromProto<&ProtoBuffData> for BuffData {
	fn try_from_proto(b: &ProtoBuffData) -> Option<Self> {
		Some(Self {
			id: BuffId::from_u32(b.get_buff_id())?,
			name: b.get_name().to_string(),
		})
	}
}

/// Information about specific effect.
#[derive(Clone)]
pub struct EffectData {
	pub id: EffectId,
	pub name: String,
	pub friendly_name: String,
	pub radius: f32,
	/// Targets affected by this effect.
	pub target: TargetType,
	/// `true` if effect affects allied units.
	pub friendly_fire: bool,
}
impl TryFromProto<&ProtoEffectData> for EffectData {
	fn try_from_proto(e: &ProtoEffectData) -> Option<Self> {
		EffectId::from_u32(e.get_effect_id()).map(|id| Self {
			id,
			name: e.get_name().to_string(),
			friendly_name: e.get_friendly_name().to_string(),
			radius: e.get_radius(),
			target: match id {
				EffectId::Null
				| EffectId::PsiStormPersistent
				| EffectId::ScannerSweep
				| EffectId::NukePersistent
				| EffectId::RavagerCorrosiveBileCP => TargetType::Any,
				_ => TargetType::Ground,
			},
			friendly_fire: matches!(
				id,
				EffectId::PsiStormPersistent | EffectId::NukePersistent | EffectId::RavagerCorrosiveBileCP
			),
		})
	}
}