Commits

Andrey Lesnikov committed 68daad0

Initial commit.

Comments (0)

Files changed (58)

+syntax:glob
+*.o
+wargame
+server
+log
+tags
+gmon.out
+MIT/X Consortium License
+
+@ 2011 Andrey Lesnikov <ozkriff@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+# See LICENSE file for copyright and license details.
+all: wargame server
+#CC       = tcc
+#CFLAGS   = -g
+#LD       = tcc
+CC       = gcc
+#CFLAGS   = -g -ansi -std=c89 -W -Wall -Wextra --pedantic
+CFLAGS   = -g
+LD       = ld
+core_obj = list.o path.o misc.o core.o net.o utype.o sc.o
+sc_obj   = sc01.o sc02.o
+obj      = $(core_obj) $(sc_obj)
+sdl_libs = -lSDL -lSDL_image -lSDL_net -lSDL_mixer -lm
+wargame: ui_sdl.o $(obj) core_full.o sc_full.o
+	$(CC) $(CFLAGS) -o wargame ui_sdl.o \
+	core_full.o sc_full.o \
+	$(sdl_libs)
+server: server.o list.o
+	$(CC) $(CFLAGS) -o server server.o list.o -lSDL_net
+list.o:   list.h
+path.o:   path.h list.h misc.h
+misc.o:   misc.h list.h core.h
+core.o:   core.h list.h core_private.h path.h net.h utype.h sc.h
+net.o:    net.h list.h core_private.h misc.h
+utype.o:  utype.h list.h core_private.h core.h
+ui_sdl.o: core.h list.h misc.h utype.h net.h path.h
+sc.o:     sc.h list.h
+sc01.o:   sc.h list.h utype.h core.h core_private.h misc.h
+sc02.o:   sc.h list.h utype.h core.h core_private.h misc.h
+server.o: list.h
+core_full.o: $(core_obj)
+	$(LD) -r $(core_obj) -o core_full.o
+sc_full.o: $(sc_obj)
+	$(LD) -r $(sc_obj) -o sc_full.o
+clean:
+	rm -f *.o wargame server log tags
+/*See LICENSE file for copyright and license details.*/
+
+typedef enum {
+  false,
+  true
+} bool;
+/*See LICENSE file for copyright and license details.*/
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <time.h>
+#include "bool.h"
+#include "list.h"
+#include "utype.h"
+#include "core.h"
+#include "path.h"
+#include "misc.h"
+#include "core_private.h"
+#include "net.h"
+#include "sc.h"
+
+Unit_type unit_types[30];
+Weapon_type weapon_types[30];
+Mcrd map_size;
+Tile *map;
+bool is_local;
+bool is_client_active;
+Unit *selected_unit;
+List units = {0, 0, 0};
+Player *current_player;
+
+static FILE *logfile;
+static Scenario *current_scenario;
+static List dead_units = {0, 0, 0};
+static List transported_units = {0, 0, 0};
+static List players = {0, 0, 0};
+static List events = {0, 0, 0};
+
+static char *s_e_move          = "MOVE u=%d dir=%d cost=%d\n";
+static char *s_e_melee         = "MELEE a=%d d=%d ak=%d dk=%d\n";
+static char *s_e_range         = "RANGE a=%d d=%d wt=%d killed=%d\n";
+static char *s_e_turn          = "TURN %d -> %d\n";
+static char *s_e_transport_in  = "TRANSPORT_IN %d %d dir:%d\n";
+static char *s_e_transport_out = "TRANSPORT_OUT %d %d dir:%d\n";
+static char *s_e_death         = "DEATH %d\n";
+static char *s_e_change_tile   = "CHANGE_TILE x:%d y:%d %d -> %d\n";
+static char *s_e_rotate        = "ROTATE %d %d\n";
+static char *s_scenario_id     = "SCENARIO: %d\n";
+
+static void
+update_fow_unit (Unit *u){
+  Mcrd m;
+  Mcrd a[100];
+  Unit_type ut = unit_types[u->t];
+  tile(u->m)->visible = true;
+  FOR_EACH_MCRD(m){
+    int diff = dir_diff(hex_dir(u->m, m), u->dir);
+    if(!tile(m)->visible && mdist(m, u->m) <= ut.v[diff]){
+      int n = get_hex_bres_array(a, u->m, m);
+      int i;
+      for(i = 1; i < n; i++){
+        Tile *t = tile(a[i]);
+        if(t){
+          t->visible = true;
+          if(t->t != T_PLAIN
+          && t->t != T_ROCKS
+          && t->t != T_WATER
+          && t->t != T_GARBAGE)
+            break;
+        }
+      }
+    }
+  }
+}
+
+static void
+update_fog_after_move (Unit *u){
+  update_fow_unit(u);
+}
+
+static void
+apply_move (Event_move e){
+  Unit *u = id2unit(e.u);
+  u->m = neib(u->m, e.dir);
+#if 0
+  u->ap -= e.cost;
+  u->stamina -= e.cost;
+#else
+  u->ap -= unit_types[u->t].ter_mv[tile(u->m)->t];
+  u->stamina -= unit_types[u->t].ter_mv[tile(u->m)->t];
+#endif
+  u->dir = e.dir;
+  fill_map(selected_unit);
+  if(u->player == current_player->id)
+    update_fog_after_move(u);
+}
+
+static int
+get_shoot_obstackes_count (Mcrd f, Mcrd t){
+  Mcrd a[100];
+  int n = get_hex_bres_array(a, f, t);
+  int total = 0;
+  int i;
+  for(i = 1; i < n - 1; i++){
+    Unit *u = unit_at(a[i]);
+    Tile *t = tile(a[i]);
+    if(t->t == T_SMALL_BUILDING
+    || t->t == T_HILL
+    || u)
+      total++;
+  }
+  return(total);
+}
+
+/*a - firer, b - target*/
+static int
+range_damage (Unit *a, Unit *b, Weapon_type_id wt_id){
+  Unit_type *at   = &unit_types[a->t];
+  Unit_type *bt   = &unit_types[b->t];
+  Weapon_type *wt = &weapon_types[wt_id];
+  int dist       = mdist(a->m, b->m);
+  int to_reach   = wt->table[dist - 1];
+  int to_hit     = at->shooting_skill * a->stamina/at->stamina;
+  int to_wound   = 4 + wt->strength - bt->toughness;
+  int to_kill    = 4 + wt->ap - bt->armor;
+  int to_pass_cover = 9 - bt->ter_bonus[ tile(b->m)->t ];
+  int shots      = a->count * wt->shots;
+  int reaches;
+  int passed_cover;
+  int hits;
+  int wounds;
+  int kills;
+  reaches = rnd_n(shots, to_reach);
+  passed_cover = rnd_n(reaches, to_pass_cover);
+  hits = rnd_n(passed_cover, to_hit);
+  wounds = rnd_n(hits, to_wound);
+  kills = rnd_n(wounds, to_kill);
+  printf("shots:%d ", shots);
+  printf(" reaches(<=%d) %d ",    to_reach,      reaches     );
+  printf(" pass cover(<=%d) %d ", to_pass_cover, passed_cover);
+  printf(" hits(<=%d) %d ",       to_hit,        hits        );
+  printf(" wounds(<=%d) %d ",     to_wound,      wounds      );
+  printf(" kills(<=%d) %d\n",     to_kill,       kills       );
+  return(kills);
+}
+
+static void
+apply_rotate (Event_rotate e){
+  Unit *u = id2unit(e.id);
+  u->dir += e.val;
+  if(u->dir < 0)
+    u->dir += 6;
+  if(u->dir >= 6)
+    u->dir -= 6;
+  u->ap -= unit_types[u->t].ter_mv[tile(u->m)->t];
+  if(u->player == current_player->id)
+    update_fow_unit(u);
+}
+
+static void
+apply_change_tile (Event_change_tile e){
+  tile(e.m)->t = e.new;
+}
+
+static void
+apply_transport_in (Event_transport_in e){
+  Unit *ut = id2unit(e.u1); /*transport*/
+  Unit *up = id2unit(e.u2); /*pas*/
+  Skill *s = find_skill(ut, S_TRANSPORT);
+  Skill_transport *ts = &s->transport;
+  Node *tmp = extruct_node(&units, data2node(units, up));
+  push_node(&transported_units, tmp);
+  push_node(&ts->units, mk_node(copy2heap(&up->id, sizeof(int))));
+  if(up == selected_unit)
+    selected_unit = NULL;
+}
+
+static Unit*
+id2transportedunit (int id){
+  Node *node;
+  FOR_EACH_NODE(transported_units, node){
+    Unit *u = node->data;
+    if(u->id == id)
+      return(u);
+  }
+  return(NULL);
+}
+
+static void
+apply_transport_out (Event_transport_out e){
+  Unit *ut = id2unit(e.u1); /*transport*/
+  Unit *up = id2transportedunit(e.u2);
+  Skill *s = find_skill(ut, S_TRANSPORT);
+  Skill_transport *ts = &s->transport;
+  Node *tmp = extruct_node(&transported_units,
+      data2node(transported_units, up));
+  push_node(&units, tmp);
+  delete_node(&ts->units, ts->units.head);
+  up->m = neib(ut->m, e.dir);
+  if(up->player == current_player->id)
+    update_fow_unit(up);
+}
+
+static void
+apply_melee (Event_melee e){
+  Unit *a = id2unit(e.a);
+  Unit *d = id2unit(e.d);
+  a->count -= e.attackers_killed;
+  d->count -= e.defenders_killed;
+  a->stamina -= 2;
+  d->stamina -= 2;
+}
+
+static void
+apply_death (Event_death e){
+  Unit *u = id2unit(e.dead_unit_id);
+  Node *n = data2node(units, u);
+  (void)extruct_node(&units, n);
+  push_node(&dead_units, n);
+  if(u == selected_unit)
+    selected_unit = NULL;
+  else
+    fill_map(selected_unit);
+}
+
+static void
+apply_range (Event_range e){
+  Unit *ua = id2unit(e.a);
+  Unit *ud = id2unit(e.d);
+  ud->count -= e.defenders_killed;
+  ua->ap -= weapon_types[e.weapon].ap_cost;
+  tile(ua->m)->visible = true;
+}
+
+static void
+updatefog (int player){
+  Mcrd m;
+  FOR_EACH_MCRD(m)
+    tile(m)->visible = false;
+  FOR_EACH_MCRD(m){
+    Node *node;
+    FOR_EACH_NODE(units, node){
+      Unit *u = node->data;
+      if(u->player == player)
+        update_fow_unit(u);
+    }
+  }
+}
+
+static bool
+is_invis (Unit *u){
+  int i;
+  if(!find_skill(u, S_INVIS)
+  || u->player == current_player->id)
+    return(false);
+  for(i=0; i<6; i++){
+    Mcrd nb = neib(u->m, i);
+    Unit *u2 = unit_at(nb);
+    if(u2 && u2->player == current_player->id)
+      return(false);
+  }
+  return(true);
+}
+
+static bool
+is_move_visible (Event_move e){
+  Unit *u = id2unit(e.u);
+  Mcrd m = neib(u->m, e.dir);
+  bool fow = tile(m)->visible || tile(u->m)->visible;
+  bool hidden = is_invis(u) && u->player != current_player->id;
+  return(!hidden && fow);
+}
+
+static bool
+is_melee_visible (Event_melee e){
+  Unit *a = id2unit(e.a);
+  Unit *d = id2unit(e.d);
+  return(tile(a->m)->visible || tile(d->m)->visible);
+}
+
+static bool
+is_range_visible (Event_range e){
+  Unit *a = id2unit(e.a);
+  Unit *d = id2unit(e.d);
+  return(tile(a->m)->visible || tile(d->m)->visible);
+}
+
+static void
+refresh_unit (Unit *u){
+  Unit_type *t = &unit_types[u->t];
+  u->ap = t->ap;
+  u->stamina += t->stamina_rg;
+  u->morale += t->morale_rg;
+  fixnum(0, t->stamina, &u->stamina);
+  fixnum(0, t->morale, &u->morale);
+}
+
+/*Search through friends in vision range and add differense
+  between friend's cost and this unit's cost to morale.
+  Search through enemies in vision range and add differense
+  between enemy's cost and this unit's cost to morale.*/
+static void
+update_unit_morale (Unit *u){
+  Unit_type *t = unit_types + u->t;
+  int plus = 0;
+  int minus = 0;
+  Node *node;
+  FOR_EACH_NODE(units, node){
+    Unit *u2 = node->data;
+    Unit_type *t2 = unit_types + u2->t;
+    int diff = 1 + (t2->cost - t->cost);
+    fixnum(0, t2->cost, &diff);
+    if(mdist(u->m, u2->m) <= t->v[diff]){
+      if(u->player == u2->player)
+        plus += diff;
+      else
+        minus += diff;
+    }
+  }
+  u->morale = u->morale + plus - minus;
+  fixnum(0, t->morale, &u->morale);
+}
+
+static void
+refresh_units (int player_id){
+  Node *node;
+  FOR_EACH_NODE(units, node){
+    Unit *u = node->data;
+    if(u->player == player_id){
+      refresh_unit(u);
+      update_unit_morale(u);
+    }
+  }
+  FOR_EACH_NODE(transported_units, node){
+    Unit *u = node->data;
+    if(u->player == player_id){
+      refresh_unit(u);
+      update_unit_morale(u);
+    }
+  }
+}
+
+static Event
+mk_event_rotate (Unit *u, int val){
+  Event e;
+  e.t = E_ROTATE;
+  e.e.rotate.id = u->id;
+  e.e.rotate.val = val;
+  return(e);
+}
+
+static Event
+mk_event_change_tile (Mcrd m, Tile_type_id old, Tile_type_id new){
+  Event e;
+  e.t = E_CHANGE_TILE;
+  e.e.change_tile.m = m;
+  e.e.change_tile.old = old;
+  e.e.change_tile.new = new;
+  return(e);
+}
+
+/*transport id, passanger id*/
+static Event
+mk_event_transport_in (int tr_id, int pas_id){
+  Unit *a = id2unit(pas_id);
+  Unit *b = id2unit(tr_id);
+  Event e;
+  e.t = E_TRANSPORT_IN;
+  e.e.transport_in.u1 = tr_id;
+  e.e.transport_in.u2 = pas_id;
+  e.e.transport_in.dir = m2dir(a->m, b->m);
+  return(e);
+}
+
+static Event
+mk_event_transport_out (int tr_id, int pas_id, Dir dir){
+  Event e;
+  e.t = E_TRANSPORT_OUT;
+  e.e.transport_out.u1 = tr_id;
+  e.e.transport_out.u2 = pas_id;
+  e.e.transport_out.dir = dir;
+  return(e);
+}
+
+/*old player's id, new player's id*/
+static Event
+mk_event_endturn (int old_id, int new_id){
+  Event e;
+  e.t = E_ENDTURN;
+  e.e.endturn.old_id = old_id;
+  e.e.endturn.new_id = new_id;
+  return(e);
+}
+
+static Event
+mk_event_move (Unit *u, int dir){
+  /*Tile_type_id tile_type = tile(neib(u->m, dir))->t;*/
+  Event e;
+  e.t = E_MOVE;
+  e.e.move.u = u->id;
+  e.e.move.dir = dir;
+#if 0
+  e.e.move.cost = unit_types[u->t].ter_mv[tile_type];
+#else
+  e.e.move.cost = 0;
+#endif
+  return(e);
+}
+
+static Event
+mk_event_melee (
+    Unit *a,
+    Unit *d, 
+    int attackers_killed,
+    int defenders_killed)
+{
+  Event e;
+  e.t = E_MELEE;
+  e.e.melee.a = a->id;
+  e.e.melee.d = d->id;
+  e.e.melee.attackers_killed = attackers_killed;
+  e.e.melee.defenders_killed = defenders_killed;
+  return(e);
+}
+
+static Event
+mk_event_range (Unit *a, Unit *d,
+    int dmg, Weapon_type_id wt){
+  Event e;
+  e.t = E_RANGE;
+  e.e.range.a = a->id;
+  e.e.range.d = d->id;
+  e.e.range.defenders_killed = dmg;
+  e.e.range.weapon = wt;
+  return(e);
+}
+
+static bool
+ambush(Mcrd next, Unit *moving_unit){
+  Unit *u = unit_at(next);
+  if(u && u->player != moving_unit->player){
+    add_event( mk_event_melee(u, moving_unit, 1, 3) );
+    return(true);
+  }else{
+    return(false);
+  }
+}
+
+#if 0
+/*[a]ttacker, [d]efender*/
+static int
+support_range (Unit *a, Unit *d){
+  int killed = 0;
+  Unit *sup;  /*[sup]porter*/
+  int i;
+  for(i=0; i<6; i++){
+    sup = unit_at( neib(d->m, i) );
+    if(sup && sup->player == d->player 
+    && find_skill(sup, S_RANGE)){
+      break;
+    }
+  }
+  if(i==6)
+    return(0);
+  if(find_skill(sup, S_RANGE)->range.shoots > 0){
+    killed = range_damage(sup, a);
+    add_event(mk_event_range(sup, a, killed));
+  }
+  return(killed);
+}
+#endif
+
+#if 0
+static int
+get_wounds (Unit *a, Unit *d){
+  Unit_type at = unit_types[a->t];
+  Unit_type dt = unit_types[d->t];
+  int tt = tile(d->m)->t; /*tile type*/
+  int hits     = 0;
+  int wounds   = 0; /*possible wounds(may be blocked by armour)*/
+  int final    = 0; /*final wounds(not blocked by armour)*/
+  int attacks  = at.attacks * a->count   * a->stamina/at.stamina;
+  int a_ms     = (at.ms + at.ter_ms[tt]) * a->stamina/at.stamina;
+  int d_ms     = (dt.ms + dt.ter_ms[tt]) * d->stamina/dt.stamina;
+  /*chances to hit, to wound and to ignore armour. percents.*/
+  int to_hit   = 5 + (a_ms - d_ms);
+  int a_stren  = at.strength * a->stamina/at.stamina;
+  int to_wound = 5 + (a_stren - dt.toughness);
+  int to_as    = 10- dt.armor;
+#if 1
+  int r = 1;
+  to_hit   += rnd(-r, r);
+  to_wound += rnd(-r, r);
+  to_as    += rnd(-r, r);
+#endif
+  fixnum(0, 9, &to_hit);
+  fixnum(0, 9, &to_wound);
+  hits   = attacks * to_hit   / 10;
+  wounds = hits    * to_as    / 10;
+  final  = wounds  * to_wound / 10;
+#if 1
+  printf("%d %d %d -> %d %d %d [%d]\n",
+      to_hit, to_wound, to_as,
+      attacks, hits, wounds, final);
+#endif
+  return(final);
+}
+#endif
+
+static Event
+mk_event_death (Unit *u){
+  Event e;
+  e.t = E_DEATH;
+  e.e.death.dead_unit_id = u->id;
+  return(e);
+}
+
+/*Check and update visibility of all units.*/
+static void
+update_units_visibility (void){
+  Node *node;
+  FOR_EACH_NODE(units, node){
+    Unit *u = node->data;
+    if(u->player == current_player->id){
+      u->is_visible = true;
+    }else{
+      u->is_visible = tile(u->m)->visible && !is_invis(u);
+    }
+  }
+}
+
+static void
+create_local_human (int id) {
+  Player *p = calloc(1, sizeof(Player));
+  p->id     = id;
+  p->is_ai  = false;
+  p->last_event_id = -1;
+  push_node(&players, mk_node(p));
+}
+
+static void
+create_local_ai (int id) {
+  Player *p = calloc(1, sizeof(Player));
+  p->id     = id;
+  p->is_ai  = true;
+  p->last_event_id = -1;
+  push_node(&players, mk_node(p));
+}
+
+/*called from add_unit*/
+static int
+get_new_unit_id (){
+  if(units.count > 0){
+    Unit *u = units.head->data;
+    return(u->id + 1);
+  }else{
+    return(0);
+  }
+}
+
+static bool
+is_event_visible (Event e){
+  switch(e.t){
+    case E_MELEE:
+      return(is_melee_visible(e.e.melee));
+    case E_RANGE:
+      return(is_range_visible(e.e.range));
+    case E_MOVE:
+      return(is_move_visible(e.e.move));
+    case E_DEATH:
+    case E_ENDTURN:
+    case E_TRANSPORT_IN:
+    case E_TRANSPORT_OUT:
+    case E_CHANGE_TILE:
+    case E_ROTATE:
+      return(true);
+    default:
+      die("core: is_event_visible(): "
+          "unknown event '%d'\n", e.t);
+      return(true);
+  }
+}
+
+/*undo all events that this player have not seen yet*/
+static void
+undo_unshown_events (){
+  Node *node = events.tail;
+  while(node){
+    Event *e = node->data;
+    if(e->id == current_player->last_event_id)
+      break;
+    undo_event(*e);
+    node = node->prev;
+  }
+}
+
+static void
+apply_endturn(Event_endturn e){
+  Node *nd;
+  FOR_EACH_NODE(players, nd){
+    Player *p = nd->data;
+    if(p->id == e.new_id){
+      if(current_player->id == e.old_id){
+        current_player = p;
+        undo_unshown_events();
+        updatefog(current_player->id);
+        update_units_visibility();
+      }else{
+        /*check_win();*/
+        refresh_units(current_player->id);
+        is_client_active = true;
+      }
+      return;
+    }
+  }
+  is_client_active = false;
+}
+
+static void
+send_ids_to_server (void){
+  int no_players_left_mark = 0xff;
+  Node *nd;
+  FOR_EACH_NODE(players, nd){
+    Player *p = nd->data;
+    send_int_as_uint8(p->id);
+  }
+  send_int_as_uint8(no_players_left_mark);
+}
+
+static void
+undo_transport_in (Event_transport_in e){
+  Unit *ut = id2unit(e.u1); /*transport*/
+  Unit *up = id2transportedunit(e.u2);
+  Skill *s = find_skill(ut, S_TRANSPORT);
+  Skill_transport *ts = &s->transport;
+  Node *tmp = extruct_node(&transported_units,
+      data2node(transported_units, up));
+  push_node(&units, tmp);
+  delete_node(&ts->units, ts->units.head);
+}
+
+static void
+undo_rotate (Event_rotate e){
+  Unit *u = id2unit(e.id);
+  u->dir -= e.val;
+  if(u->dir < 0)
+    u->dir += 6;
+  if(u->dir >= 6)
+    u->dir -= 6;
+  u->ap += unit_types[u->t].ter_mv[tile(u->m)->t];
+}
+
+static void
+undo_change_tile (Event_change_tile e){
+  tile(e.m)->t = e.old;
+}
+
+static void
+undo_transport_out (Event_transport_out e){
+  Unit *ut = id2unit(e.u1); /*transport*/
+  Unit *up = id2unit(e.u2); /*pas*/
+  Skill *s = find_skill(ut, S_TRANSPORT);
+  Skill_transport *ts = &s->transport;
+  Node *tmp = extruct_node(&units, data2node(units, up));
+  push_node(&transported_units, tmp);
+  push_node(&ts->units, mk_node(copy2heap(&up->id, sizeof(int))));
+}
+
+static void
+undo_move (Event_move e){
+  Unit *u = id2unit(e.u);
+  int dir = e.dir + 3;
+  if(dir >= 6)
+    dir -= 6;
+  u->m = neib(u->m, dir);
+#if 0
+  u->ap += e.cost;
+  u->stamina += e.cost;
+#else
+  u->ap += unit_types[u->t].ter_mv[tile(u->m)->t];
+  u->stamina += unit_types[u->t].ter_mv[tile(u->m)->t];
+#endif
+  fill_map(selected_unit);
+  if(u->player == current_player->id)
+    updatefog(current_player->id);
+}
+
+static void
+undo_melee (Event_melee e){
+  Unit *a = id2unit(e.a);
+  Unit *d = id2unit(e.d);
+  a->count += e.attackers_killed;
+  d->count += e.defenders_killed;
+  a->stamina += 2;
+  d->stamina += 2;
+  a->ap += weapon_types[ unit_types[a->t].weapons[0] ].ap_cost;
+}
+
+static void
+undo_range (Event_range e){
+  Unit *a = id2unit(e.a);
+  Unit *d = id2unit(e.d);
+  d->count += e.defenders_killed;
+  a->ap += weapon_types[e.weapon].ap_cost;
+}
+
+static void
+undo_death (Event_death e){
+  Node *n = pop_node(&dead_units);
+  Unit *u = n->data;
+  if(u->id != e.dead_unit_id){
+    die("core: undo_death(): "
+        "u->id=%d e.dead_unit_id=%d\n",
+        u->id,
+        e.dead_unit_id);
+  }
+  push_node(&units, n);
+  fill_map(selected_unit);
+}
+
+void
+undo_event (Event e){
+  switch(e.t){
+    case E_MOVE:
+      undo_move(e.e.move);
+      break;
+    case E_MELEE:
+      undo_melee(e.e.melee);
+      break;
+    case E_ENDTURN:
+      /*empty*/
+      break;
+    case E_DEATH:
+      undo_death(e.e.death);
+      break;
+    case E_RANGE:
+      undo_range(e.e.range);
+      break;
+    case E_TRANSPORT_IN:
+      undo_transport_in(e.e.transport_in);
+      break;
+    case E_TRANSPORT_OUT:
+      undo_transport_out(e.e.transport_out);
+      break;
+    case E_CHANGE_TILE:
+      undo_change_tile(e.e.change_tile);
+      break;
+    case E_ROTATE:
+      undo_rotate(e.e.rotate);
+      break;
+    default:
+      die("core: undo_event(): "
+          "unknown event type '%d'\n", e.t);
+      break; 
+  }
+  update_units_visibility();
+}
+
+void
+select_next_unit (void){
+  Node *node;
+  Unit *u;
+  if(selected_unit)
+    node = data2node(units, selected_unit);
+  else
+    node = units.head;
+  u = node->data;
+  do{
+    node = node->next ? node->next : units.head;
+    u = node->data;
+  }while(u->player != current_player->id);
+  fill_map(selected_unit = u);
+}
+
+void
+add_event_local (Event data){
+  Node *tmp = mk_node(copy2heap(&data, sizeof(Event)));
+  add_node_to_tail(&events, tmp);
+}
+
+Event
+s2event (char *s){
+  Event e;
+  if(strcmp_sp(s, s_e_move)){
+    e.t = E_MOVE;
+    sscanf(s, s_e_move,
+        &e.e.move.u,
+        &e.e.move.dir,
+        &e.e.move.cost);
+  }else if(strcmp_sp(s, s_e_melee)){
+    e.t = E_MELEE;
+    sscanf(s, s_e_melee,
+        &e.e.melee.a,
+        &e.e.melee.d,
+        &e.e.melee.attackers_killed,
+        &e.e.melee.defenders_killed);
+  }else if(strcmp_sp(s, s_e_range)){
+    e.t = E_RANGE;
+    sscanf(s, s_e_range, 
+        &e.e.range.a,
+        &e.e.range.d,
+        &e.e.range.weapon,
+        &e.e.range.defenders_killed);
+  }else if(strcmp_sp(s, s_e_turn)){
+    e.t = E_ENDTURN;
+    sscanf(s, s_e_turn,
+        &e.e.endturn.old_id,
+        &e.e.endturn.new_id);
+  }else if(strcmp_sp(s, s_e_transport_in)){
+    e.t = E_TRANSPORT_IN;
+    sscanf(s, s_e_transport_in,
+        &e.e.transport_in.u1,
+        &e.e.transport_in.u2,
+        &e.e.transport_in.dir);
+  }else if(strcmp_sp(s, s_e_transport_in)){
+    e.t = E_TRANSPORT_IN;
+    sscanf(s, s_e_transport_in,
+        &e.e.transport_in.u1,
+        &e.e.transport_in.u2,
+        &e.e.transport_in.dir);
+  }else if(strcmp_sp(s, s_e_death)){
+    e.t = E_DEATH;
+    sscanf(s, s_e_death,
+        &e.e.death.dead_unit_id);
+  }else if(strcmp_sp(s, s_e_change_tile)){
+    e.t = E_CHANGE_TILE;
+    sscanf(s, s_e_change_tile,
+        &e.e.change_tile.m.x,
+        &e.e.change_tile.m.y,
+        &e.e.change_tile.old,
+        &e.e.change_tile.new);
+  }else if(strcmp_sp(s, s_e_rotate)){
+    e.t = E_ROTATE;
+    sscanf(s, s_e_rotate,
+        &e.e.rotate.id,
+        &e.e.rotate.val);
+  }else{
+    die("core: s2event(): unknown event.\n");
+  }
+  return(e);
+}
+
+void
+event2log (Event e){
+  switch(e.t){
+    case E_MOVE:
+      fprintf(logfile, s_e_move,
+          e.e.move.u,
+          e.e.move.dir,
+          e.e.move.cost);
+      break;
+    case E_MELEE:
+      fprintf(logfile, s_e_melee,
+          e.e.melee.a,
+          e.e.melee.d,
+          e.e.melee.attackers_killed,
+          e.e.melee.defenders_killed);
+       break;
+    case E_RANGE:
+      fprintf(logfile, s_e_range,
+          e.e.range.a,
+          e.e.range.d,
+          e.e.range.weapon,
+          e.e.range.defenders_killed);
+      break;
+    case E_ENDTURN:
+      fprintf(logfile, s_e_turn,
+          e.e.endturn.old_id,
+          e.e.endturn.new_id);
+      break;
+    case E_TRANSPORT_IN:
+      fprintf(logfile, s_e_transport_in,
+          e.e.transport_in.u1,
+          e.e.transport_in.u2,
+          e.e.transport_in.dir);
+      break;
+    case E_TRANSPORT_OUT:
+      fprintf(logfile, s_e_transport_out,
+          e.e.transport_out.u1,
+          e.e.transport_out.u2,
+          e.e.transport_out.dir);
+      break;
+    case E_DEATH:
+      fprintf(logfile, s_e_death,
+          e.e.death.dead_unit_id);
+      break;
+    case E_CHANGE_TILE:
+      fprintf(logfile, s_e_change_tile,
+          e.e.change_tile.m.x,
+          e.e.change_tile.m.y,
+          e.e.change_tile.old,
+          e.e.change_tile.new);
+      break;
+    case E_ROTATE:
+      fprintf(logfile, s_e_rotate,
+          e.e.rotate.id,
+          e.e.rotate.val);
+      break;
+    default:
+      die("core: event2log(): "
+          "unknown event '%d'\n", e.t);
+      break;
+   }
+}
+
+int
+get_new_event_id (void){
+  if(events.count > 0){
+    Event *last = events.tail->data;
+    return(last->id + 1);
+  }else{
+    return(0);
+  }
+}
+
+void
+view_replay (char *filename){
+  FILE *f = fopen(filename, "r");
+  int id;
+  char s[300];
+  if(f == NULL)
+    die("core: view_replay(): bad filename.\n");
+  fgets(s, 300, f);
+  sscanf(s, s_scenario_id, &id);
+  set_scenario_id(id);
+  while(fgets(s, 300, f)){
+    Event e = s2event(s);
+    /*event2log(e);*/
+    add_event(e);
+  }
+  fclose(f);
+}
+
+void
+add_event (Event e){
+  e.id = get_new_event_id();
+  add_event_local(e);
+  event2log(e);
+  if(!is_local)
+    send_event(e);
+}
+
+void
+cleanup (void){
+  fclose(logfile);
+  logfile = NULL;
+  clear_list(&players);
+  clear_list(&players);
+  clear_list(&events);
+  clear_list(&units);
+  clear_list(&dead_units);
+}
+
+/*Used in apply_invisible_events(). TODO: rename.*/
+static Node *
+get_next_event_node(){
+  Node *node;
+  if(events.count == 0)
+    return(NULL);
+  /*this player haven't seen any events*/
+  if(current_player->last_event_id == -1)
+    return(events.head);
+  /*find last seen event*/
+  FOR_EACH_NODE(events, node){ 
+    Event *e = node->data;
+    if(e->id == current_player->last_event_id)
+      break;
+  }
+  if(!node)
+    return(NULL);
+  else
+    return(node->next);
+}
+
+static void
+apply_invisible_events (void){
+  Node *node = get_next_event_node();
+  while(node){
+    Event *e = node->data;
+    if(!is_event_visible(*e))
+      apply_event(*e);
+    else
+      break;
+    node = node->next;
+  }
+}
+
+/*Always called after apply_invisible_events*/
+Event *
+get_next_event (void){
+  Node *node;
+  Event *e = NULL;
+  if(events.count == 0)
+    return(NULL);
+  if(current_player->last_event_id == -1){
+    e = events.head->data;
+    return(e);
+  }
+  FOR_EACH_NODE(events, node){ 
+    e = node->data;
+    if(e->id == current_player->last_event_id){
+      e = node->next->data;
+      return(e);
+    }
+  }
+  return(NULL);
+}
+
+void
+endturn (void){
+  int id = current_player->id + 1;
+  if(id == current_scenario->players_count)
+    id = 0;
+  selected_unit = NULL;
+  add_event(mk_event_endturn(current_player->id, id));
+}
+
+void
+move (Unit *moving_unit, Mcrd destination){
+  List path;
+  Node *node;
+  if(tile(destination)->cost > moving_unit->ap)
+    return;
+  path = get_path(destination);
+  for(node = path.head; node->next; node = node->next){
+    Mcrd *next    = node->next->data;
+    Mcrd *current = node->data;
+    Dir dir = m2dir(*current, *next);
+    if(ambush(*next, moving_unit))
+      break;
+    /*TODO*/
+    if(moving_unit->t == U_LIGHT_TANK
+    && tile(*next)->t == T_BUSH){
+      Tile_type_id old = tile(*current)->t;
+      Tile_type_id new = T_PLAIN;
+      add_event(mk_event_change_tile(*next, old, new));
+    }
+    add_event(mk_event_move(moving_unit, dir));
+  }
+  clear_list(&path);
+}
+
+static Weapon_type_id
+get_best_weapon_type (Unit *a, Unit *d){
+  if(a->t == U_RIFLE_SQUAD && d->t == U_LIGHT_TANK){
+    return(W_MOLOTOV);
+  }else if(a->t == U_LIGHT_TANK){
+    if(d->t == U_RIFLE_SQUAD
+    || d->t == U_SUBMACHINE_SQUAD
+    || d->t == U_SCOUT)
+      return(W_HEAVY_MACHINE_GUN);
+    else
+      return(W_TANK_CANNON);
+  }else{
+    return(unit_types[a->t].weapons[0]);
+  }
+}
+
+/*[a]ttacker, [d]efender*/
+void
+attack (Unit *a, Unit *d){
+  int dist = mdist(a->m, d->m);
+  Weapon_type_id wt_id = get_best_weapon_type(a, d);
+  Weapon_type *wt = &weapon_types[wt_id];
+  int obst = get_shoot_obstackes_count(a->m, d->m);
+  if(obst == 0 && dist <= wt->range && a->ap >= wt->ap_cost){
+    int targets_killed = range_damage(a, d, wt_id);
+    add_event( mk_event_range(a, d, targets_killed, wt_id) );
+    if(d->count - targets_killed <= 0){
+      add_event(mk_event_death(d));
+      if(d->t == U_LIGHT_TANK || d->t == U_TRUCK){
+        Tile_type_id old = tile(d->m)->t;
+        Tile_type_id new = T_BROKEN_CAR;
+        add_event(mk_event_change_tile(d->m, old, new));
+      }
+    }
+  }
+}
+
+void
+apply_event (Event e){
+  current_player->last_event_id = e.id;
+  switch(e.t){
+    case E_MOVE:
+      apply_move(e.e.move);
+      break;
+    case E_MELEE:
+      apply_melee(e.e.melee);
+      break;
+    case E_RANGE:
+      apply_range(e.e.range);
+      break;
+    case E_ENDTURN:
+      apply_endturn(e.e.endturn);
+      break;
+    case E_DEATH:
+      apply_death(e.e.death);
+      break;
+    case E_TRANSPORT_IN:
+      apply_transport_in(e.e.transport_in);
+      break;
+    case E_TRANSPORT_OUT:
+      apply_transport_out(e.e.transport_out);
+      break;
+    case E_CHANGE_TILE:
+      apply_change_tile(e.e.change_tile);
+      break;
+    case E_ROTATE:
+      apply_rotate(e.e.rotate);
+      break;
+    default:
+      die("core: apply_event(): "
+          "unknown event '%d'\n", e.t);
+      break;
+  }
+  update_units_visibility();
+}
+
+void
+add_unit (Mcrd m, Dir d, int plr, Unit_type_id t_id) {
+  Unit *u        = calloc(1, sizeof(Unit));
+  Unit_type t    = unit_types[t_id];
+  u->player      = plr;
+  u->ap          = t.ap;
+  u->count       = t.count;
+  u->skills_n    = t.skills_n;
+  u->stamina     = t.stamina;
+  u->morale      = t.morale;
+  u->m           = m;
+  u->t           = t_id;
+  u->id          = get_new_unit_id();
+  u->dir         = d;
+  memcpy(u->skills, t.skills, u->skills_n * sizeof(Skill));
+  push_node(&units, mk_node(u));
+}
+
+/*Called before get_next_event*/
+bool
+unshown_events_left (void){
+  apply_invisible_events();
+  if(events.count == 0){
+    return(false);
+  }else{
+    Event *e = events.tail->data;
+    return(e->id != current_player->last_event_id);
+  }
+}
+
+void
+init_local_players (int n, int *ids){
+  int i;
+  for(i=0; i<n; i++)
+    create_local_human(ids[i]);
+}
+
+void
+init_net (char *host, int port){
+  init_network(host, port);
+  send_ids_to_server();
+  set_scenario_id(get_scenario_from_server());
+  is_local = false;
+  /*add_event_local(mk_event_endturn(0, 0));*/
+}
+
+void
+mark_ai (int id){
+  int i = 0;
+  Node *nd;
+  FOR_EACH_NODE(players, nd){
+    if(i==id){
+      Player *p = nd->data;
+      p->is_ai = true;
+      return;
+    }
+    i++;
+  }
+}
+
+/*example: init_local_players_s("hh", 0, 1);*/
+void
+init_local_players_s (char *s, ...){
+  va_list ap;
+  char *c = s;
+  va_start(ap, s);
+  while(c && *c){
+    int id = va_arg(ap, int);
+    if(*c=='h'){
+      create_local_human(id);
+    }else if(*c=='a'){
+      create_local_ai(id);
+    }else{
+      die("core: init_players_s(): %c, %d\n", *c, *c);
+    }
+    c++;
+  }
+  va_end(ap);
+  current_player = players.tail->data;
+}
+
+void
+set_scenario_id (int id){
+  current_player = players.tail->data;
+  current_scenario = scenarios + id;
+  map_size = current_scenario->map_size;
+  current_scenario->init();
+  updatefog(current_player->id);
+  update_units_visibility();
+  if(logfile)
+    die("core: set_scenario_id(): logfile != NULL.\n");
+  logfile = fopen("log", "w");
+  fprintf(logfile, s_scenario_id, id);
+  if(current_player->id == 0)
+    is_client_active = true;
+  else
+    is_client_active = false;
+}
+
+void
+init (void){
+  srand( (unsigned int)time(NULL) );
+  init_unit_types();
+  init_weapon_types();
+  init_scenarios();
+  is_local = true;
+  is_client_active = true;
+  selected_unit = NULL;
+  current_player = NULL;
+  logfile = NULL;
+  current_scenario = NULL;
+}
+
+Tile *
+tile (Mcrd c){
+  return(map + map_size.x*c.y + c.x);
+}
+
+/*is tile inboard*/
+bool
+inboard (Mcrd t){
+  bool is_x_ok = t.x >= 0 && t.x < map_size.x;
+  bool is_y_ok = t.y >= 0 && t.y < map_size.y;
+  return(is_x_ok && is_y_ok);
+}
+
+Unit *
+id2unit (int id){
+  Node *node;
+  FOR_EACH_NODE(units, node){
+    Unit *u = node->data;
+    if(u->id == id)
+      return(u);
+  }
+  return(NULL);
+}
+
+Unit *
+unit_at (Mcrd crd){
+  Node *node;
+  FOR_EACH_NODE(units, node){
+    Unit *u = node->data;
+    if(mcrdeq(u->m, crd))
+      return(u);
+  }
+  return(NULL);
+}
+
+static Unit*
+get_neib_transport (Mcrd m){
+  int dir;
+  for(dir = 0; dir < 6; dir++){
+    Unit *u = unit_at(neib(m, dir));
+    if(u && find_skill(u, S_TRANSPORT))
+      return(u);
+  }
+  return(NULL);
+}
+
+void
+transport_in (Unit *u){
+  Unit *tr = get_neib_transport(u->m);
+  if(u->t != U_RIFLE_SQUAD
+  && u->t != U_SUBMACHINE_SQUAD)
+    return;
+  if(tr){
+    Skill_transport *s = 
+        &find_skill(tr, S_TRANSPORT)->transport;
+    if(s->units.count < s->capacity){
+      Event e = mk_event_transport_in(tr->id, u->id);
+      add_event(e);
+    }
+  }
+}
+
+void
+transport_out (Unit *transport){
+  Skill *s = find_skill(transport, S_TRANSPORT);
+  Skill_transport sk = s->transport;
+  if(sk.units.count > 0){
+    int *id = sk.units.head->data;
+    Unit *up = id2transportedunit(*id);
+    int dir;
+    for(dir = 0; dir < 6; dir++){
+      Mcrd m = neib(transport->m, dir);
+      if(inboard(m) && !unit_at(m))
+          break;
+    }
+    if(dir != 6){
+      Event e = mk_event_transport_out(
+          transport->id, up->id, dir);
+      add_event(e);
+    }
+  }
+}
+
+void
+rotate (Unit *u, int index){
+  int cost = unit_types[u->t].ter_mv[tile(u->m)->t];
+  if(u->ap >= cost){
+    add_event(mk_event_rotate(u, index));
+  }
+}
+/*See LICENSE file for copyright and license details.*/
+
+typedef struct { int x, y; } Vec2i;
+
+/*map coordinates*/
+typedef Vec2i Mcrd; 
+
+typedef enum { D_UR, D_R, D_DR, D_DL, D_L, D_UL } Dir;
+
+/* skills */
+typedef enum {
+  S_INVIS,
+  S_IGNR,
+  S_NORETURN,
+  S_TRANSPORT
+} Skill_id;
+
+/*TODO может тащить ченить на прицепе в штатном режиме?*/
+typedef struct {
+  List units;
+  int capacity;
+} Skill_transport;
+
+typedef struct {
+  Skill_id t;
+  Skill_transport transport;
+} Skill;
+
+typedef struct {
+  int v[4]; /*range of vision*/
+  int morale;
+  int morale_rg; /*regeneration per turn*/
+  int count;
+  int ms; /*melee_skill*/
+  int strength;
+  int toughness;
+  int shooting_skill;
+#if 0
+  List weapons;
+#else
+  Weapon_type_id weapons[10];
+  int weapons_n;
+#endif
+  int armor;
+  int ter_mv[10]; /*terrain move cost*/
+  int ter_bonus[10]; /*bonuses at different terrains*/
+  Skill skills[10];
+  int skills_n;
+  int stamina; /*max unit's stamina*/
+  int stamina_rg; /*regeneration per turn*/
+  int exp; /*experiense level*/
+  int cost;
+  int ap; /*action points*/
+} Unit_type;
+
+typedef struct {
+  int range;
+  int strength;
+  int ap; /*armor piercing ability*/
+  int ap_cost; /*cost in action points*/
+  int table[20]; /*accuracy table*/
+  int shots; /*used in damage calculating */
+#if 0
+  List attacks;
+#endif
+} Weapon_type;
+
+typedef struct {
+  Unit_type_id t;
+  int id;
+  int count;
+  int player;
+  int ap;
+  Mcrd m;
+  Skill skills[10];
+  int skills_n;
+  bool is_visible;
+  int stamina;
+  int morale;
+  int dir;
+} Unit;
+
+typedef enum {
+  T_PLAIN,
+  T_TREES,
+  T_WATER,
+  T_ROCKS,
+  T_HILL,
+  T_GARBAGE,
+  T_SMALL_BUILDING,
+  T_BROKEN_CAR,
+  T_BUSH,
+  T_SHELL_HOLE
+} Tile_type_id;
+
+typedef struct {
+  bool visible; /*fog of war*/
+  int cost; /* cost of path for selunit to this tile */
+  Tile_type_id t;
+  Mcrd parent; /* used in pathfinding */
+  Dir dir;
+} Tile;
+
+typedef enum {
+  E_MOVE,
+  E_MELEE,
+  E_RANGE,
+  E_ENDTURN,
+  E_DEATH,
+  E_TRANSPORT_IN,
+  E_TRANSPORT_OUT,
+  E_CHANGE_TILE,
+  E_ROTATE
+} Event_type_id;
+
+typedef struct {
+  int id;
+  int val;
+} Event_rotate;
+
+typedef struct {
+  Tile_type_id old, new;
+  Mcrd m;
+} Event_change_tile;
+
+typedef struct {
+  int u1; /*transport id*/
+  int u2; /*passenger id*/
+  Dir dir; /*pas -> transp*/
+} Event_transport_in;
+
+typedef struct {
+  int u1; /*transport id*/
+  int u2; /*passenger id*/
+  Dir dir; /*transp -> pas*/
+} Event_transport_out;
+
+typedef struct {
+  int u; /*unit's id*/
+  int dir; /*direction index*/
+  int cost;
+} Event_move;
+
+typedef struct {
+  int a, d; /* attacker, defender */
+  int attackers_killed;
+  int defenders_killed;
+} Event_melee;
+
+typedef struct {
+  Weapon_type_id weapon;
+  int a, d;
+  int defenders_killed;
+} Event_range;
+
+typedef struct {
+  int old_id;
+  int new_id;
+} Event_endturn;
+
+typedef struct {
+  int dead_unit_id;
+} Event_death;
+
+typedef struct {
+  Event_type_id t;
+  int id;
+  union {
+    Event_move move;
+    Event_melee melee;
+    Event_range range;
+    Event_endturn endturn;
+    Event_death death;
+    Event_transport_in transport_in;
+    Event_transport_out transport_out;
+    Event_change_tile change_tile;
+    Event_rotate rotate;
+  } e;
+} Event;
+
+typedef struct {
+  int id;
+  bool is_ai;
+  int last_event_id;
+} Player;
+
+typedef struct {
+  int players_count;
+  Mcrd map_size;
+  void (*init)(void);
+  void (*logic)(void);
+} Scenario;
+
+extern Mcrd map_size;
+extern Unit *selected_unit;
+extern Unit_type unit_types[30];
+extern Weapon_type weapon_types[30];
+extern bool is_local;
+extern bool is_client_active;
+extern Player *current_player;
+extern List units;
+extern Tile *map;
+
+void init (void);
+void cleanup (void);
+
+void rotate (Unit *u, int index);
+void move (Unit *moving_unit, Mcrd destination);
+void attack (Unit *a, Unit *d);
+void select_next_unit (void);
+void endturn (void);
+void transport_in (Unit *u);
+void transport_out (Unit *transport);
+
+bool unshown_events_left (void);
+Event *get_next_event (void);
+void apply_event (Event e);
+void undo_event ();
+
+void init_local_players (int n, int *ids);
+void init_local_players_s (char *s, ...);
+void set_scenario_id (int id);
+void init_net (char *host, int port);
+
+int get_new_event_id (void);
+
+Tile  *tile (Mcrd c);
+bool   inboard (Mcrd t);
+Unit  *id2unit (int id);
+Unit  *unit_at (Mcrd crd);
+
+void   view_replay (char *filename);
+/*See LICENSE file for copyright and license details.*/
+
+void  add_event_local (Event data);
+void  add_event (Event e);
+void  event2log (Event e);
+void  add_unit (Mcrd m, Dir d, int player, Unit_type_id t_id);

img/arrow.png

Added
New image

img/blood.png

Added
New image

img/broken_car.png

Added
New image

img/bush.png

Added
New image

img/d0.png

Added
New image

img/d1.png

Added
New image

img/d2.png

Added
New image

img/d3.png

Added
New image

img/d4.png

Added
New image

img/d5.png

Added
New image

img/fog.png

Added
New image

img/font_16x24.png

Added
New image

img/font_8x12.png

Added
New image

img/garbage.png

Added
New image

img/hill.png

Added
New image

img/plain.png

Added
New image

img/reacheble.png

Added
New image

img/rifle_squad.png

Added
New image

img/ring_blue.png

Added
New image

img/ring_red.png

Added
New image

img/rocks.png

Added
New image

img/scout.png

Added
New image

img/sel.png

Added
New image

img/shell_hole.png

Added
New image

img/small_building.png

Added
New image

img/submachine_squad.png

Added
New image

img/tank.png

Added
New image

img/trees.png

Added
New image

img/truck.png

Added
New image

img/water.png

Added
New image
+/*See LICENSE file for copyright and license details.*/
+
+/*Double-linked list, stack, queue.*/
+
+#include <malloc.h>
+#include "bool.h"
+#include "list.h"
+
+/*Create in heap node that points to 'data' and return
+  pointer to rhis node.*/
+Node *
+mk_node (void *data){
+  Node *n = malloc(sizeof(Node));
+  n->data = data;
+  n->next = NULL;
+  n->prev = NULL;
+  return(n); 
+}
+
+/*If 'after' is NULL, then 'node' will be added at the head
+  of the list, else it will be added following 'after'.*/
+void
+insert_node (List *list, Node *node, Node *after){
+  if(after){
+    node->next = after->next;
+    node->prev = after;
+    after->next = node;
+  }else{
+    node->next = list->head;
+    node->prev = NULL;
+    list->head = node;
+  }
+  if(node->next)
+    node->next->prev = node;
+  else
+    list->tail = node;
+  list->count++;
+}
+
+/*Extructs node from list, returns pointer to this node.*/
+Node *
+extruct_node (List *list, Node *node){
+  if(!node)
+    return(NULL);
+  if(node->next)
+    node->next->prev = node->prev;
+  else
+    list->tail = node->prev;
+  if(node->prev)
+    node->prev->next = node->next;
+  else
+    list->head = node->next;
+  list->count--;
+  return(node);
+}
+
+/*Delete data and node.*/
+void
+delete_node (List *list, Node *node){
+  Node *tmp = extruct_node(list, node);
+  free(tmp->data);
+  free(tmp);
+}
+
+/*Extruct node from list, delete node,
+  return pointer to data.*/
+void *
+extruct_data (List *list, Node *node){
+  Node *tmp = extruct_node(list, node);
+  void *data = node->data;
+  free(tmp);
+  return(data);
+}
+
+void
+clear_list (List *l){
+  while(l->count)
+    delete_node(l, l->head);
+}
+
+Node *
+data2node (List l, void *data){
+  Node *node;
+  FOR_EACH_NODE(l, node)
+    if(node->data == data)
+      return(node);
+  return(NULL);
+}
+/*See LICENSE file for copyright and license details.*/
+
+/*Double-linked list, stack, queue.*/
+
+typedef struct Node Node;
+struct Node {
+  Node *next;
+  Node *prev;
+  void *data;
+};
+
+typedef struct List List;
+struct List {
+  Node *head; /*pointer to first node*/
+  Node *tail; /*pointer to last node*/
+  int count; /*number of nodes in list*/
+};
+
+void  insert_node (List *list, Node *node, Node *after);
+Node *extruct_node(List *list, Node *node);
+void  delete_node (List *list, Node *node);
+void *extruct_data(List *list, Node *node);
+
+Node *mk_node (void *data);
+Node *data2node (List l, void *data);
+void  clear_list (List *l);
+
+#define add_node_to_head(list, node) \
+  insert_node(list, node, NULL)
+#define add_node_after(list, node, after) \
+  insert_node(list, node, after)
+#define add_node_to_tail(list, node) \
+  insert_node(list, node, (list)->tail)
+
+#define Stack          List
+#define push_node      add_node_to_head
+#define pop_node(list) extruct_node(list, (list)->head)
+
+#define Queue          List
+#define enq_node       add_node_to_tail
+#define deq_node(list) extruct_node(list, (list)->head)
+
+#define FOR_EACH_NODE(list, node) \
+  for(node=(list).head; node; node=node->next)
+/*See LICENSE file for copyright and license details.*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include "bool.h"
+#include "list.h"
+#include "utype.h"
+#include "core.h"
+#include "misc.h"
+
+void
+die(const char *errstr, ...){
+  va_list ap;
+  va_start(ap, errstr);
+  vfprintf(stderr, errstr, ap);
+  va_end(ap);
+  exit(EXIT_FAILURE);
+}
+
+int
+str2int (char *str) {
+  int n;
+  if(sscanf(str, "%d", &n) != 1)
+    die("str2int error: \'%s\'\n", str);
+  return(n);
+}
+
+Mcrd
+mk_mcrd (int x, int y){
+  Mcrd m;
+  m.x = x;
+  m.y = y;
+  return(m);
+}
+
+Skill *
+find_skill(Unit *u, Skill_id type){
+  int i;
+  for(i=0; i<u->skills_n; i++){
+    if(u->skills[i].t == type)
+      return(&u->skills[i]);
+  }
+  return(NULL);
+}
+
+/*is mcrd equal*/
+bool
+mcrdeq(Mcrd a, Mcrd b){
+  return(a.x == b.x && a.y == b.y); 
+}
+
+/*Get distance between tiles.*/
+int
+mdist (Mcrd a, Mcrd b) {
+  int dx, dy;
+  a.x += a.y/2; 
+  b.x += b.y/2;
+  dx = b.x-a.x;
+  dy = b.y-a.y;
+  return( (abs(dx) + abs(dy) + abs(dx-dy)) / 2 );
+}
+
+/*Get tile's neiborhood by it's index.*/
+Mcrd
+neib (Mcrd a, int neib_index) {
+  int d[2][6][2] = {
+    { {1,-1}, {1,0}, {1,1}, { 0,1}, {-1,0}, { 0,-1}, },
+    { {0,-1}, {1,0}, {0,1}, {-1,1}, {-1,0}, {-1,-1}, } };
+  int dx = d[a.y%2][neib_index][0];
+  int dy = d[a.y%2][neib_index][1];
+  return( mk_mcrd(a.x+dx, a.y+dy) );
+}
+
+/*Get tile's opposite neiborhood.*/
+int
+opposite_neib_index (int i){
+  int d[] = {3, 4,2, 5,1, 0};
+  return(i + d[i]);
+}
+
+/*Find neiborhood's Dir Index*/
+Dir
+m2dir(Mcrd a, Mcrd b){
+  int i;
+  for(i=0; i<6; i++){
+    if(mcrdeq(neib(a,i), b))
+      return(i);
+  }
+  die("misc: m2dir.");
+  return(0);
+}
+
+void
+fixnum (int min, int max, int *n){
+  if(*n < min)
+    *n = min;
+  if(*n > max)
+    *n = max;
+}
+
+int
+rnd (int min, int max){
+  if(max != min)
+    return(rand()%(max-min)+min);
+  else
+    return(max);
+}
+
+/*Compare strings till first space or tab.
+  strcmp_sp("lo 2 3", "lo %d %d") -> true
+  strcmp_sp("no 2 3", "lo %d %d") -> false */
+bool
+strcmp_sp (char *s1, char *s2){
+  while(s2 && s1){
+    if(*s1 != *s2)
+      return(false);
+    if(*s1==' ' || *s1=='\t')
+      return(true);
+    s1++, s2++;
+  }
+  return(true);
+}
+
+/*get tile type corresponding to character*/
+Tile_type_id
+char2tiletype (char c){
+  switch(c){
+    case '.': return(T_PLAIN);
+    case 't': return(T_TREES);
+    case 'w': return(T_WATER);
+    case 'r': return(T_ROCKS);
+    case 'h': return(T_HILL);
+    case 'g': return(T_GARBAGE);
+    case 'b': return(T_SMALL_BUILDING);
+    case 'c': return(T_BROKEN_CAR);
+    case '*': return(T_BUSH);
+    case 's': return(T_SHELL_HOLE);
+    default:
+      die("misc: char2tiletype(): "
+          "unknown tiletype '%c'\n", c);
+  }
+  return(0);
+}
+
+Tile *
+str2map (char *s){
+  Tile *i; /*used for iteration through 'map'*/
+  Tile *map = malloc(sizeof(Tile)
+      * map_size.x * map_size.y);
+  i = map;
+  while(s && *s!=0){
+    if(*s!=' ' && *s!='\n' && *s!='\0'){
+      i->t = char2tiletype(*s);
+      i++;
+    }
+    s++;
+  }
+  return(map);
+}
+
+/*sx - x sign, sy - y sign*/
+static Dir
+get_bres_dir(int sx, int sy){
+       if(sx == -1 && sy == -1) return(D_UL);
+  else if(sx ==  0 && sy == -1) return(D_L );
+  else if(sx ==  1 && sy == -1) return(D_UR);
+  else if(sx == -1 && sy ==  1) return(D_DL);
+  else if(sx ==  0 && sy ==  1) return(D_R );
+  else if(sx ==  1 && sy ==  1) return(D_DR);
+  die("misc: get_bres_dir(): Bad parameters.");
+  return(-1);
+}
+
+/*TODO: rename me*/
+void*
+copy2heap (void *data, int size){
+  void *tmp = malloc(size);
+  memcpy(tmp, data, size);
+  return(tmp);
+}
+
+/*f-from, t-to, c-current, e-error,
+  sx-sign_x..
+  http://zvold.blogspot.com/2010/01/
+  bresenhams-line-drawing-algorithm-on_26.html*/
+List
+get_hex_bres (Mcrd f, Mcrd t){
+  List line = {NULL, NULL, 0};
+  int e = 0;
+  int dy_old = t.y - f.y;
+  int dx_old = 2 * (t.x - f.x) - abs(t.y%2) + abs(f.y%2);
+  int dy = 3 * abs(dy_old);
+  int dx = 3 * abs(dx_old);
+  int sx = (dx_old > 0) ? 1 : -1;
+  int sy = (dy_old > 0) ? 1 : -1;
+  bool is_horisontal = abs(dy) < abs(dx);
+  Mcrd c = f;
+  push_node(&line, copy2heap(&c, sizeof(Mcrd)));
+  while(c.x != t.x || c.y != t.y){
+    if(is_horisontal){
+      e += dy;
+      if(e > abs(dx_old)){
+        c = neib(c, get_bres_dir(sx, sy));
+        e -= dx;
+      }else{
+        c = neib(c, get_bres_dir(0, sx));
+        e += dy;
+      }
+    }else{
+      e += dx;
+      if(e > 0){
+        c = neib(c, get_bres_dir(sx, sy));
+        e -= dy;
+      }else{
+        c = neib(c, get_bres_dir(-sx, sy));
+        e += dy;
+      }
+    }
+    push_node(&line, copy2heap(&c, sizeof(Mcrd)));
+  }
+  return(line);
+}
+
+/*f-from, t-to, c-current, e-error,
+  sx-sign_x..
+  http://zvold.blogspot.com/2010/01/
+  bresenhams-line-drawing-algorithm-on_26.html*/
+int
+get_hex_bres_array (Mcrd *array, Mcrd f, Mcrd t){
+  int i = 0; /*array index*/
+  int e = 0;
+  int dy_old = t.y - f.y;
+  int dx_old = 2 * (t.x - f.x) - abs(t.y%2) + abs(f.y%2);
+  int dy = 3 * abs(dy_old);
+  int dx = 3 * abs(dx_old);
+  int sx = (dx_old > 0) ? 1 : -1;
+  int sy = (dy_old > 0) ? 1 : -1;
+  bool is_horisontal = abs(dy) < abs(dx);
+  Mcrd c = f;
+  array[0] = c;
+  i++;
+  while(c.x != t.x || c.y != t.y){
+    if(is_horisontal){
+      e += dy;
+      if(e > abs(dx_old)){
+        c = neib(c, get_bres_dir(sx, sy));
+        e -= dx;
+      }else{
+        c = neib(c, get_bres_dir(0, sx));
+        e += dy;
+      }
+    }else{
+      e += dx;
+      if(e > 0){
+        c = neib(c, get_bres_dir(sx, sy));
+        e -= dy;
+      }else{
+        c = neib(c, get_bres_dir(-sx, sy));
+        e += dy;
+      }
+    }
+    array[i] = c;
+    i++;
+  }
+  return(i);
+}
+
+
+/*N times test if [0..9] is less or equal to val.*/
+int
+rnd_n (int n, int val){
+  int i;
+  int result = 0;
+  for(i = 0; i < n; i++){
+    int x = rand() % 10;
+    if(x <= val)
+      result++;
+  }
+  return(result);
+}
+
+Dir
+hex_dir (Mcrd f, Mcrd t){
+  int dy = t.y - f.y;
+  int dx4 = (2 * (t.x - f.x) - abs(t.y%2) + abs(f.y%2)) / 4;
+  int dx2 = 2 * (t.x - f.x) - abs(t.y%2) + abs(f.y%2);
+  bool is_horisontal = abs(dy) <= abs(dx4); /*<= 30 degr*/
+  bool sx = dx2 > 0;
+  bool sy = dy > 0;
+  if(is_horisontal){
+    if(sx)
+      return(D_R);
+    else
+      return(D_L);
+  }else{
+    if(sx){
+      if(sy)
+        return(D_DR);
+      else
+        return(D_UR);
+    }else{
+      if(sy)
+        return(D_DL);
+      else
+        return(D_UL);
+    }
+  }
+}
+
+int
+dir_diff (Dir d0, Dir d1){
+  int diff = abs(d0 - d1);
+  if(diff > 3)
+    diff = 6 - diff;
+  return(diff);
+}
+/*See LICENSE file for copyright and license details.*/
+
+#define FOR_EACH_MCRD(mc) \
+  for(mc.y=0; mc.y<map_size.y; mc.y++) \
+    for(mc.x=0; mc.x<map_size.x; mc.x++)
+
+void   die(const char *errstr, ...);
+int    str2int (char *str);
+Mcrd   mk_mcrd (int x, int y);
+bool   mcrdeq (Mcrd a, Mcrd b);
+int    mdist (Mcrd a, Mcrd b);
+Mcrd   neib (Mcrd a, int i);
+int    opposite_neib_index (int i);
+Dir    m2dir (Mcrd a, Mcrd b);
+void   fixnum (int min, int max, int *n);
+int    rnd (int min, int max);
+bool   strcmp_sp (char *s1, char *s2);
+Tile  *str2map (char *s);
+Tile_type_id char2tiletype(char c);
+Skill *find_skill (Unit *u, Skill_id type);
+
+List get_hex_bres (Mcrd f, Mcrd t);
+int get_hex_bres_array (Mcrd *array, Mcrd f, Mcrd t);
+void* copy2heap (void *data, int size);
+
+int rnd_n (int n, int val);
+
+Dir hex_dir (Mcrd f, Mcrd t);
+int dir_diff (Dir d0, Dir d1);
+/*See LICENSE file for copyright and license details.*/
+
+#include <stdlib.h>
+#include "SDL/SDL_net.h"
+#include "bool.h"
+#include "list.h"
+#include "utype.h"
+#include "core.h"
+#include "misc.h"
+#include "core_private.h"
+#include "net.h"
+
+#define Byte uint8_t
+
+static TCPsocket socket;
+static SDLNet_SocketSet sockets;
+
+static Event
+mk_event_move (Byte *d){
+  Event e;
+  e.t = E_MOVE;