@@ -191,9 +191,9 @@ impl Iterator for SpiralIter {
}
}

pub fn distance<P: AsRef<MapPos>>(from: &P, to: &P) -> ZInt {
let to = to.as_ref().v;
let from = from.as_ref().v;
pub fn distance(from: &MapPos, to: &MapPos) -> ZInt {
let to = to.v;
let from = from.v;
let dx = (to.x + to.y / 2) - (from.x + from.y / 2);
let dy = to.y - from.y;
(dx.abs() + dy.abs() + (dx - dy).abs()) / 2
@@ -8,7 +8,7 @@ use map::{Map, Terrain};
use partial_state::{PartialState};
use game_state::{GameState};
use dir::{Dir, dirs};
use ::{MovePoints, MapPos, ExactPos, SlotId, get_free_exact_pos};
use ::{MovePoints, ExactPos, SlotId, ObjectClass, get_free_exact_pos};

#[derive(Clone)]
pub struct Tile {
@@ -36,12 +36,15 @@ impl Default for Tile {
pub fn truncate_path(db: &Db, state: &PartialState, path: &[ExactPos], unit: &Unit) -> Vec<ExactPos> {
let mut new_path = Vec::new();
let mut cost = MovePoints{n: 0};
for pos in path {
cost.n += tile_cost(db, state, unit, pos).n;
new_path.push(path[0].clone());
for window in path.windows(2) {
let from = &window[0];
let to = &window[1];
cost.n += tile_cost(db, state, unit, from, to).n;
if cost.n > unit.move_points.n {
break;
}
new_path.push(pos.clone());
new_path.push(to.clone());
}
new_path
}
@@ -50,53 +53,83 @@ pub fn path_cost<S: GameState>(db: &Db, state: &S, unit: &Unit, path: &[ExactPos
-> MovePoints
{
let mut cost = MovePoints{n: 0};
for node in path {
cost.n += tile_cost(db, state, unit, node).n;
for window in path.windows(2) {
let from = &window[0];
let to = &window[1];
cost.n += tile_cost(db, state, unit, from, to).n;
}
cost

}

// TODO: const (see https://github.com/rust-lang/rust/issues/24111 )
pub fn max_cost() -> MovePoints {
MovePoints{n: ZInt::max_value()}
}

pub fn obstacles_count<S: GameState>(
state: &S,
pos: &MapPos,
) -> ZInt {
let units = state.units_at(pos);
let objects = state.objects_at(pos);
let mut count = units.len() + objects.len();
for unit in &units {
for obj in &objects {
if unit.pos == obj.pos {
count -= 1;
}
}
}
count as ZInt
}

pub fn tile_cost<S: GameState>(db: &Db, state: &S, unit: &Unit, pos: &ExactPos)
pub fn tile_cost<S: GameState>(db: &Db, state: &S, unit: &Unit, from: &ExactPos, pos: &ExactPos)
-> MovePoints
{
let obstacles_count = obstacles_count(state, &pos.map_pos);
let unit_type = db.unit_type(&unit.type_id);
let map_pos = &pos.map_pos;
let objects = state.objects_at(map_pos);
let units = state.units_at(map_pos);
let mut unit_cost = 0;
let mut object_cost = 0;
'unit_loop: for unit in &units {
for object in &objects {
match object.pos.slot_id {
SlotId::Id(_) => if unit.pos == object.pos {
assert_eq!(unit_type.class, UnitClass::Infantry);
break 'unit_loop;
},
SlotId::TwoTiles(_) | SlotId::WholeTile => {
break 'unit_loop;
},
}
}
unit_cost += 1;
}
let tile = state.map().tile(&pos);
let n = match unit_type.class {
let mut terrain_cost = match unit_type.class {
UnitClass::Infantry => match *tile {
Terrain::Plain => 1,
Terrain::Trees => 2,
Terrain::City => 0,
Terrain::Plain | Terrain::City => 4,
Terrain::Trees => 5,
},
UnitClass::Vehicle => match *tile {
Terrain::Plain => 1,
Terrain::Trees => 5,
Terrain::City => 0,
Terrain::Plain | Terrain::City => 4,
Terrain::Trees => 8,
},
};
MovePoints{n: n + obstacles_count}
for object in &objects {
if unit_type.class == UnitClass::Vehicle
&& object.class == ObjectClass::Road
{
let mut i = object.pos.map_pos_iter();
let road_from = i.next().unwrap();
let road_to = i.next().unwrap();
assert!(road_from != road_to);
let is_road_pos_ok = road_from == from.map_pos
&& road_to == pos.map_pos;
if is_road_pos_ok && !unit_type.is_big {
terrain_cost = 2; // TODO: ultrahardcoded value :(
}
}
}
for object in &objects {
let cost = match unit_type.class {
UnitClass::Infantry => match object.class {
ObjectClass::Building => 1,
ObjectClass::Road => 0,
},
UnitClass::Vehicle => match object.class {
ObjectClass::Building => 2,
ObjectClass::Road => 0,
},
};
object_cost += cost;
}
MovePoints{n: terrain_cost + object_cost + unit_cost}
}

pub struct Pathfinder {
@@ -125,7 +158,7 @@ impl Pathfinder {
neighbour_pos: &ExactPos
) {
let old_cost = self.map.tile(&original_pos).cost.clone();
let tile_cost = tile_cost(db, state, unit, neighbour_pos);
let tile_cost = tile_cost(db, state, unit, original_pos, neighbour_pos);
let tile = self.map.tile_mut(&neighbour_pos);
let new_cost = MovePoints{n: old_cost.n + tile_cost.n};
if tile.cost.n > new_cost.n {
@@ -194,14 +227,13 @@ impl Pathfinder {
*/

pub fn get_path(&self, destination: &ExactPos) -> Option<Vec<ExactPos>> {
let mut path = Vec::new();
let mut path = vec![destination.clone()];
let mut pos = destination.clone();
if self.map.tile(&pos).cost.n == max_cost().n {
return None;
}
while self.map.tile(&pos).cost.n != 0 {
assert!(self.map.is_inboard(&pos));
path.push(pos.clone());
let parent_dir = match *self.map.tile(&pos).parent() {
Some(ref dir) => dir,
None => return None,
@@ -211,6 +243,7 @@ impl Pathfinder {
map_pos: neighbour_map_pos.clone(),
slot_id: self.map.tile(&neighbour_map_pos).slot_id.clone(),
};
path.push(pos.clone());
}
path.reverse();
if path.is_empty() {
@@ -208,7 +208,7 @@ impl EventAttackUnitVisualizer {
if let Some(ref attacker_id) = attack_info.attacker_id {
let attacker_node_id = scene.unit_id_to_node_id(attacker_id);
let attacker_pos = scene.node(&attacker_node_id).pos;
let attacker_map_pos = state.unit(attacker_id).pos.clone();
let attacker_map_pos = state.unit(attacker_id).pos.map_pos.clone();
if let core::FireMode::Reactive = attack_info.mode {
map_text.add_text(&attacker_map_pos, "reaction fire");
}
@@ -223,23 +223,26 @@ impl EventAttackUnitVisualizer {
&attacker_pos, &defender_pos, shell_speed));
}
if attack_info.is_ambush {
map_text.add_text(&defender.pos, "Ambushed");
map_text.add_text(&defender.pos.map_pos, "Ambushed");
};
let is_target_destroyed = defender.count - attack_info.killed <= 0;
if attack_info.killed > 0 {
map_text.add_text(&defender.pos, &format!("-{}", attack_info.killed));
map_text.add_text(
&defender.pos.map_pos,
&format!("-{}", attack_info.killed),
);
} else {
map_text.add_text(&defender.pos, "miss");
map_text.add_text(&defender.pos.map_pos, "miss");
}
let is_target_suppressed = defender.morale < 50
&& defender.morale + attack_info.suppression >= 50;
if !is_target_destroyed {
map_text.add_text(
&defender.pos,
&defender.pos.map_pos,
&format!("morale: -{}", attack_info.suppression),
);
if is_target_suppressed {
map_text.add_text(&defender.pos, "suppressed");
map_text.add_text(&defender.pos.map_pos, "suppressed");
}
}
Box::new(EventAttackUnitVisualizer {
@@ -322,7 +325,7 @@ impl EventShowUnitVisualizer {
marker_mesh_id: &MeshId,
map_text: &mut MapTextManager,
) -> Box<EventVisualizer> {
map_text.add_text(&unit_info.pos, "spotted");
map_text.add_text(&unit_info.pos.map_pos, "spotted");
show_unit_at(db, scene, unit_info, mesh_id, marker_mesh_id);
Box::new(EventShowUnitVisualizer)
}
@@ -347,8 +350,8 @@ impl EventHideUnitVisualizer {
unit_id: &UnitId,
map_text: &mut MapTextManager,
) -> Box<EventVisualizer> {
let pos = state.unit(unit_id).pos.clone();
map_text.add_text(&pos, "lost");
let pos = &state.unit(unit_id).pos.map_pos;
map_text.add_text(pos, "lost");
scene.remove_unit(unit_id);
Box::new(EventHideUnitVisualizer)
}
@@ -380,7 +383,7 @@ impl EventUnloadUnitVisualizer {
unit_type_visual_info: &UnitTypeVisualInfo,
map_text: &mut MapTextManager,
) -> Box<EventVisualizer> {
map_text.add_text(&unit_info.pos, "unloaded");
map_text.add_text(&unit_info.pos.map_pos, "unloaded");
let to = geom::exact_pos_to_world_pos(&unit_info.pos);
let from = geom::exact_pos_to_world_pos(transporter_pos);
show_unit_at(db, scene, unit_info, mesh_id, marker_mesh_id);
@@ -424,7 +427,7 @@ impl EventLoadUnitVisualizer {
map_text: &mut MapTextManager,
) -> Box<EventVisualizer> {
let unit_pos = &state.unit(unit_id).pos;
map_text.add_text(unit_pos, "loaded");
map_text.add_text(&unit_pos.map_pos, "loaded");
let from = geom::exact_pos_to_world_pos(unit_pos);
let to = geom::exact_pos_to_world_pos(transporter_pos);
let passenger_node_id = scene.unit_id_to_node_id(unit_id);
@@ -463,7 +466,7 @@ impl EventSetReactionFireModeVisualizer {
mode: &ReactionFireMode,
map_text: &mut MapTextManager,
) -> Box<EventVisualizer> {
let unit_pos = &state.unit(unit_id).pos;
let unit_pos = &state.unit(unit_id).pos.map_pos;
match *mode {
ReactionFireMode::Normal => {
map_text.add_text(unit_pos, "Normal fire mode");
@@ -3,6 +3,7 @@
use std::f32::consts::{PI};
use cgmath::{Vector3, Rad, Angle, rad};
use core::{ExactPos, MapPos, SlotId, geom};
use core::dir::{Dir};
use types::{ZInt, ZFloat, VertexCoord, WorldPos};

pub use core::geom::{HEX_IN_RADIUS, HEX_EX_RADIUS};
@@ -17,6 +18,12 @@ pub fn map_pos_to_world_pos(p: &MapPos) -> WorldPos {
pub fn exact_pos_to_world_pos(p: &ExactPos) -> WorldPos {
let v = geom::map_pos_to_world_pos(&p.map_pos).extend(0.0);
match p.slot_id {
SlotId::TwoTiles(ref dir) => {
// TODO: employ index_to_circle_vertex_rnd
let p2 = Dir::get_neighbour_pos(&p.map_pos, dir);
let v2 = geom::map_pos_to_world_pos(&p2).extend(0.0);
WorldPos{v: (v + v2) / 2.0}
}
SlotId::WholeTile => {
WorldPos{v: v + index_to_circle_vertex_rnd(3, 0, &p.map_pos).v * 0.2}
}
@@ -39,18 +39,18 @@ impl MapTextManager {
}
}

pub fn add_text<P: AsRef<MapPos>>(&mut self, pos: &P, text: &str) {
pub fn add_text(&mut self, pos: &MapPos, text: &str) {
self.commands.push_back(ShowTextCommand {
pos: pos.as_ref().clone(),
pos: pos.clone(),
text: text.to_owned(),
});
}

fn can_show_text_here<P: AsRef<MapPos>>(&self, pos: &P) -> bool {
fn can_show_text_here(&self, pos: &MapPos) -> bool {
let min_progress = 0.3;
for map_text in self.visible_labels_list.values() {
let progress = map_text.move_helper.progress();
if map_text.pos == *pos.as_ref() && progress < min_progress {
if map_text.pos == *pos && progress < min_progress {
return false;
}
}
@@ -39,6 +39,7 @@ use core::{
MapPos,
ExactPos,
SlotId,
ObjectClass,
check_command,
get_unit_ids_at,
find_next_player_unit_id,
@@ -221,6 +222,22 @@ fn get_shell_mesh(context: &mut Context) -> Mesh {
Mesh::new(context, &vertices, &indices, texture)
}

fn get_road_mesh(context: &mut Context) -> Mesh {
let w = geom::HEX_EX_RADIUS * 0.3;
let l = geom::HEX_EX_RADIUS;
let h = geom::MIN_LIFT_HEIGHT / 2.0;
let vertices = [
Vertex{pos: [-w, -l, h], uv: [0.0, 0.0]},
Vertex{pos: [-w, l, h], uv: [0.0, 1.0]},
Vertex{pos: [w, l, h], uv: [1.0, 1.0]},
Vertex{pos: [w, -l, h], uv: [1.0, 0.0]},
];
let indices = [0, 1, 2, 2, 3, 0];
let texture_data = fs::load("road.png").into_inner();
let texture = load_texture(&mut context.factory, &texture_data);
Mesh::new(context, &vertices, &indices, texture)
}

fn get_marker<P: AsRef<Path>>(context: &mut Context, tex_path: P) -> Mesh {
let n = 0.2;
let vertices = [
@@ -257,6 +274,7 @@ fn get_marker_mesh_id<'a>(mesh_ids: &'a MeshIdManager, player_id: &PlayerId) ->
struct MeshIdManager {
big_building_mesh_w_id: MeshId,
building_mesh_w_id: MeshId,
road_mesh_id: MeshId,
trees_mesh_id: MeshId,
shell_mesh_id: MeshId,
marker_1_mesh_id: MeshId,
@@ -390,6 +408,7 @@ impl TacticalScreen {
&mut meshes, load_object_mesh(context, "trees"));
let shell_mesh_id = add_mesh(
&mut meshes, get_shell_mesh(context));
let road_mesh_id = add_mesh(&mut meshes, get_road_mesh(context));
let marker_1_mesh_id = add_mesh(
&mut meshes, get_marker(context, "flag1.png"));
let marker_2_mesh_id = add_mesh(
@@ -413,6 +432,7 @@ impl TacticalScreen {
big_building_mesh_w_id: big_building_mesh_w_id,
building_mesh_w_id: building_mesh_w_id,
trees_mesh_id: trees_mesh_id,
road_mesh_id: road_mesh_id,
shell_mesh_id: shell_mesh_id,
marker_1_mesh_id: marker_1_mesh_id,
marker_2_mesh_id: marker_2_mesh_id,
@@ -484,6 +504,7 @@ impl TacticalScreen {
let state = &player_info.game_state;
let map = state.map();
for tile_pos in map.get_iter() {
let objects = state.objects_at(&tile_pos);
if let Terrain::Trees = *map.tile(&tile_pos) {
let pos = geom::map_pos_to_world_pos(&tile_pos);
let rot = rad(thread_rng().gen_range(0.0, PI * 2.0));
@@ -494,20 +515,37 @@ impl TacticalScreen {
children: Vec::new(),
});
}
if let Terrain::City = *map.tile(&tile_pos) {
let objects = state.objects_at(&tile_pos);
for object in objects {
let pos = geom::exact_pos_to_world_pos(&object.pos);
let rot = rad(thread_rng().gen_range(0.0, PI * 2.0));
player_info.scene.add_node(SceneNode {
pos: pos,
rot: rot,
mesh_id: Some(match object.pos.slot_id {
SlotId::Id(_) => self.mesh_ids.building_mesh_w_id.clone(),
SlotId::WholeTile => self.mesh_ids.big_building_mesh_w_id.clone(),
}),
children: Vec::new(),
});
for object in objects {
match object.class {
ObjectClass::Building => {
let pos = geom::exact_pos_to_world_pos(&object.pos);
let rot = rad(thread_rng().gen_range(0.0, PI * 2.0));
player_info.scene.add_node(SceneNode {
pos: pos,
rot: rot,
mesh_id: Some(match object.pos.slot_id {
SlotId::Id(_) => self.mesh_ids.building_mesh_w_id.clone(),
SlotId::WholeTile => self.mesh_ids.big_building_mesh_w_id.clone(),
SlotId::TwoTiles(_) => unimplemented!(),
}),
children: Vec::new(),
});
}
ObjectClass::Road => {
let pos = geom::exact_pos_to_world_pos(&object.pos);
let rot = match object.pos.slot_id {
SlotId::TwoTiles(ref dir) => {
rad(dir.to_int() as ZFloat * PI / 3.0 + PI / 6.0)
},
_ => panic!(),
};
player_info.scene.add_node(SceneNode {
pos: pos,
rot: rot,
mesh_id: Some(self.mesh_ids.road_mesh_id.clone()),
children: Vec::new(),
});
}
}
}
}