From 1912d10a566fe5cea1b4e1d26180518c3b1420eb Mon Sep 17 00:00:00 2001
From: johns <>
Date: Mon, 24 Apr 2000 15:48:57 +0000
Subject: [PATCH] New enemy detection code and rescue of units implemented, bug
 in change unit owner fixed

---
 include/sound.h        |   3 +
 include/unit.h         |   8 ++
 sound/script_sound.cpp |   4 +
 sound/sound.cpp        |  12 ++-
 stratagus/mainloop.cpp |  70 +++++++++------
 stratagus/player.cpp   |  52 +++++++++++
 ui/mouse.cpp           |   5 +-
 unit/unit.cpp          | 191 ++++++++++++++++++++++++++++++++++++++++-
 8 files changed, 314 insertions(+), 31 deletions(-)

diff --git a/include/sound.h b/include/sound.h
index 190cf8e2c..c50b6200c 100644
--- a/include/sound.h
+++ b/include/sound.h
@@ -50,6 +50,9 @@ typedef struct _game_sound_ {
     SoundConfig HumanWorkComplete;	/// building ready
     SoundConfig PeasantWorkComplete;	/// building ready
     SoundConfig OrcWorkComplete;	/// building ready
+
+    SoundConfig HumanRescue;		/// rescue units
+    SoundConfig OrcRescue;		/// rescue units
 } GameSound;
 
 /*----------------------------------------------------------------------------
diff --git a/include/unit.h b/include/unit.h
index 84519dfc6..fd37178b1 100644
--- a/include/unit.h
+++ b/include/unit.h
@@ -355,7 +355,13 @@ extern void UpdateForNewUnit(const Unit* unit,int upgrade);
 extern void NearestOfUnit(const Unit* unit,int tx,int ty,int *dx,int *dy);
 extern int UnitVisible(const Unit* unit);
 extern void RemoveUnit(Unit* unit);
+    /// Increment mana of all magic units each second.
 extern void UnitIncrementMana(void);
+    /// Increment health of all regenerating units each second.
+extern void UnitIncrementHealth(void);
+    /// Check for rescue each second.
+extern void RescueUnits(void);
+    /// Change owner of unit.
 extern void ChangeUnitOwner(Unit* unit,Player* old,Player* new);
 
 extern void UnitNewHeading(Unit* unit);
@@ -397,6 +403,8 @@ extern int ViewPointDistanceToUnit(Unit* dest);
 
     /// Return true, if unit is an enemy of the player
 extern int IsEnemy(const Player* player,const Unit* dest);
+    /// Return true, if unit is allied with the player
+extern int IsAllied(const Player* player,const Unit* dest);
 extern int CanTarget(const UnitType* type,const UnitType* dest);
 
 extern void SaveUnit(const Unit* unit,FILE* file);	/// save unit-structure
diff --git a/sound/script_sound.cpp b/sound/script_sound.cpp
index 29f167d1f..14463e71a 100644
--- a/sound/script_sound.cpp
+++ b/sound/script_sound.cpp
@@ -270,6 +270,10 @@ local SCM CclDefineGameSounds(SCM list) {
 	} else if ( gh_eq_p(name,gh_symbol2scm("placement-success")) ) {
 	    GameSounds.PlacementSuccess.Sound=CCL_SOUND_ID(data);
 	    DebugLevel3("SoundPlacementSuccess %d\n",SoundPlacementSuccess);
+	} else if ( gh_eq_p(name,gh_symbol2scm("human-rescue")) ) {
+	    GameSounds.HumanRescue.Sound=CCL_SOUND_ID(data);
+	} else if ( gh_eq_p(name,gh_symbol2scm("orc-rescue")) ) {
+	    GameSounds.OrcRescue.Sound=CCL_SOUND_ID(data);
 	} else {
    	    fprintf(stderr,"Incorrect symbol\n");
 	    return list;
diff --git a/sound/sound.cpp b/sound/sound.cpp
index 4e362c04a..3fb42e438 100644
--- a/sound/sound.cpp
+++ b/sound/sound.cpp
@@ -63,7 +63,9 @@ global GameSound GameSounds={
     { "building construction"},
     { "basic human voices work complete" },
     { "peasant work complete" },
-    { "basic orc voices work complete" }
+    { "basic orc voices work complete" },
+    { "rescue (human)" },
+    { "rescue (orc)" },
 };
 
 /*----------------------------------------------------------------------------
@@ -277,6 +279,14 @@ global void InitSoundClient(void)
 	GameSounds.OrcWorkComplete.Sound=
 		SoundIdForName(GameSounds.OrcWorkComplete.Name);
     }
+    if( !GameSounds.HumanRescue.Sound ) {
+	GameSounds.HumanRescue.Sound=
+		SoundIdForName(GameSounds.HumanRescue.Name);
+    }
+    if( !GameSounds.OrcRescue.Sound ) {
+	GameSounds.OrcRescue.Sound=
+		SoundIdForName(GameSounds.OrcRescue.Name);
+    }
 }
 
 #endif	// } WITH_SOUND
diff --git a/stratagus/mainloop.cpp b/stratagus/mainloop.cpp
index e9423d656..b331602a7 100644
--- a/stratagus/mainloop.cpp
+++ b/stratagus/mainloop.cpp
@@ -9,14 +9,17 @@
 //	   FreeCraft - A free fantasy real time strategy game engine
 //
 /**@name mainloop.c	-	The main game loop. */
-/*
-**	(c) Copyright 1998-2000 by Lutz Sammer
-**
-**	$Id$
-*/
+//
+//	(c) Copyright 1998-2000 by Lutz Sammer
+//
+//	$Id$
 
 //@{
 
+//----------------------------------------------------------------------------
+//	Includes
+//----------------------------------------------------------------------------
+
 #include <stdio.h>
 
 #include "freecraft.h"
@@ -44,18 +47,30 @@
 #include <SDL/SDL.h>
 #endif
 
-/* variable set when we are scrolling via keyboard */
+//----------------------------------------------------------------------------
+//	Variables
+//----------------------------------------------------------------------------
+
+    /// variable set when we are scrolling via keyboard
 global enum _scroll_state_ KeyScrollState=ScrollNone;
 
-/* variable set when we are scrolling via mouse */
+    /// variable set when we are scrolling via mouse
 global enum _scroll_state_ MouseScrollState=ScrollNone;
 
+//----------------------------------------------------------------------------
+//	Functions
+//----------------------------------------------------------------------------
+
 /**
 **	Handle scrolling area.
+**
+**	@param TempScrollState	Scroll direction/state.
+**	@param FastScroll	Flag scroll faster.
+**
+**	FIXME: Support dynamic acceleration of scroll speed.
 */
 local void DoScrollArea(enum _scroll_state_ TempScrollState, int FastScroll)
 {
-
     switch( TempScrollState ) {
 	case ScrollUp:
 	    if( MapY ) {
@@ -257,7 +272,9 @@ global void UpdateDisplay(void)
 	    DrawMissiles();
 	    SetClipping(0,0,VideoWidth,VideoHeight);
 	}
+
 	// FIXME: trick17! must find a better solution
+	// Resources over map!
 	if( TheUI.MapX<TheUI.ResourceX && TheUI.MapWidth>TheUI.ResourceX ) {
 	    MustRedraw|=RedrawResources;
 	}
@@ -278,7 +295,7 @@ global void UpdateDisplay(void)
 		,TheUI.MenuButton.Graphic->Width
 		,TheUI.MenuButton.Graphic->Height
 		,TheUI.MenuButtonX,TheUI.MenuButtonY);
-	// FIXME: Button position is configured
+
 	DrawMenuButton(MBUTTON_MAIN, (ButtonUnderCursor == 0
 		? MenuButtonActive : 0)|
 		(GameMenuButtonClicked ? MenuButtonClicked : 0),
@@ -437,20 +454,22 @@ global void GameMainLoop(void)
 
 	    MustRedraw&=~RedrawMinimap;	// FIXME: this a little hack!
 
-	    /*
-	    **	Called each second. Split into different frames.
-	    **		Increment mana of magic units.
-	    **		Update mini-map.
-	    **		Update map fog of war.
-	    **		Call AI.
-	    **		Check game goals.
-	    */
+	    //
+	    //	Work todo each second.
+	    //		Split into different frames, to reduce cpu time.
+	    //		Increment mana of magic units.
+	    //		Update mini-map.
+	    //		Update map fog of war.
+	    //		Call AI.
+	    //		Check game goals.
+	    //		Check rescue of units.
+	    //
 	    switch( FrameCounter%FRAMES_PER_SECOND ) {
 		case 0:
 		    UnitIncrementMana();	// magic units
 		    break;
 		case 1:
-		    //UnitIncrementHealth();// berserker healing
+		    UnitIncrementHealth();	// berserker healing
 		    break;
 		case 2:
 		    MapUpdateVisible();
@@ -467,8 +486,14 @@ global void GameMainLoop(void)
 		case 6:
 		    RegenerateForest();
 		    break;
+		case 7:
+		    RescueUnits();
+		    break;
 	    }
 	}
+	//
+	//	Map scrolling
+	//
 	if( TheUI.MouseScroll && !(FrameCounter%SpeedMouseScroll) ) {
 	    DoScrollArea(MouseScrollState, 0);
 	}
@@ -502,16 +527,9 @@ global void GameMainLoop(void)
 
 	WaitEventsAndKeepSync();
 
+#ifndef NEW_NETWORK
 	NetworkSync();			// FIXME: wrong position
-#if 0
-	//
-	//	Sync:	not needed done by DoEvent
-	//
-	while( VideoSyncSpeed && VideoInterrupts<1 ) {
-	    sigpause(0);
-	}
 #endif
-
 	VideoInterrupts=0;
     }
 }
diff --git a/stratagus/player.cpp b/stratagus/player.cpp
index af670ebe7..97b4bb0af 100644
--- a/stratagus/player.cpp
+++ b/stratagus/player.cpp
@@ -151,6 +151,58 @@ global void CreatePlayer(char* name,int type)
     player->Enemy=0;
     player->Allied=0;
     player->AiNum=PlayerAiUniversal;
+
+    //
+    //	Calculate enemy/allied mask.
+    //
+    for( i=0; i<NumPlayers; ++i ) {
+	switch( type ) {
+	    case PlayerNeutral:
+	    case PlayerNobody:
+	    default:
+		break;
+	    case PlayerComputer:
+		// Computer allied with computer and enemy of all humans.
+		if( Players[i].Type==PlayerComputer ) {
+		    player->Allied|=(1<<i);
+		    Players[i].Allied|=(1<<NumPlayers);
+		} else if( Players[i].Type==PlayerHuman
+			|| Players[i].Type==PlayerRescueActive ) {
+		    player->Enemy|=(1<<i);
+		    Players[i].Enemy|=(1<<NumPlayers);
+		}
+		break;
+	    case PlayerHuman:
+		// Humans are enemy of all?
+		if( Players[i].Type==PlayerComputer
+			|| Players[i].Type==PlayerHuman ) {
+		    player->Enemy|=(1<<i);
+		    Players[i].Enemy|=(1<<NumPlayers);
+		} else if( Players[i].Type==PlayerRescueActive
+			|| Players[i].Type==PlayerRescuePassive ) {
+		    player->Allied|=(1<<i);
+		    Players[i].Allied|=(1<<NumPlayers);
+		}
+		break;
+	    case PlayerRescuePassive:
+		// Rescue passive are allied with humans
+		if( Players[i].Type==PlayerHuman ) {
+		    player->Allied|=(1<<i);
+		    Players[i].Allied|=(1<<NumPlayers);
+		}
+		break;
+	    case PlayerRescueActive:
+		// Rescue active are allied with humans and enemies of computer
+		if( Players[i].Type==PlayerComputer ) {
+		    player->Enemy|=(1<<i);
+		    Players[i].Enemy|=(1<<NumPlayers);
+		} else if( Players[i].Type==PlayerHuman ) {
+		    player->Allied|=(1<<i);
+		    Players[i].Allied|=(1<<NumPlayers);
+		}
+		break;
+	}
+    }
     
     //
     //	Initial default resources.
diff --git a/ui/mouse.cpp b/ui/mouse.cpp
index 6784d40cf..909dfa15c 100644
--- a/ui/mouse.cpp
+++ b/ui/mouse.cpp
@@ -288,12 +288,11 @@ global void DoRightButton(int x,int y)
         //
         if( action==MouseActionAttack ) {
             // FIXME: more units on same tile
+	    // FIXME: should use enemy first, than other functions!
             dest=TargetOnMapTile(unit,x,y);
             if( dest ) {
                 dest->Blink=3;
-                if( dest->Player==ThisPlayer || 
-		        dest->Player->Player==PlayerNumNeutral) {
-		    // FIXME: lokh: maybe we should add the ally players here
+                if( dest->Player==ThisPlayer || IsAllied(ThisPlayer,dest) ) {
 		    SendCommandFollow(unit,dest,flush);
                     continue;
                 } else {
diff --git a/unit/unit.cpp b/unit/unit.cpp
index 962db2f39..808931d3e 100644
--- a/unit/unit.cpp
+++ b/unit/unit.cpp
@@ -328,7 +328,9 @@ global Unit* MakeUnit(UnitType* type,Player* player)
     if( !type->Building ) {
         unit->Heading=(MyRand()>>13)&7;	// random heading
         player->NumFoodUnits++;		// food needed
-	MustRedraw|=RedrawResources;	// update food
+	if( player==ThisPlayer ) {
+	    MustRedraw|=RedrawResources;// update food
+	}
     } else {
 	player->NumBuildings++;
     }
@@ -529,6 +531,7 @@ global void UnitLost(const Unit* unit)
     if( unit->Type->Building ) {
 	// FIXME: This should be complete rewritten
 	// FIXME: Slow and new members are available
+	// FIXME: most redraws only needed for player==ThisPlayer
 
 	// Still under construction
 	if( unit->Command.Action!=UnitActionBuilded ) {
@@ -799,8 +802,57 @@ global void UnitIncrementMana(void)
 #endif
 }
 
+/**
+**	Increment health of all regenerating units. Called each second.
+*/
+global void UnitIncrementHealth(void)
+{
+#ifdef NEW_UNIT
+    Unit** table;
+    Unit* unit;
+    static UnitType* berserker;
+    static int regeneration;
+
+    if( !berserker ) {			// FIXME: can move to init code!
+	berserker=UnitTypeByIdent("unit-berserker");
+	regeneration=UpgradeIdByIdent("upgrade-berserker-regeneration");
+    }
+
+    for( table=Units; table<Units+NumUnits; table++ ) {
+	unit=*table;
+	if( unit->Type==berserker
+		&& unit->HP<unit->Stats->HitPoints
+		&& UpgradeIdAllowed(unit->Player,regeneration)=='R' ) {
+	    ++unit->HP;			// FIXME: how fast do we regenerate
+	}
+    }
+#else
+    Unit* unit;
+    int i;
+    static UnitType* berserker;
+    static int regeneration;
+
+    if( !berserker ) {
+	berserker=UnitTypeByIdent("unit-berserker");
+	regeneration=UpgradeIdByIdent("upgrade-berserker-regeneration");
+    }
+
+    for( i=0; i< NumUnits; i++) {
+	unit=Units[i];
+	if( unit->Type==berserker
+		&& unit->HP<unit->Stats->HP
+		&& UpgradeIdAllowed(unit->Player,regeneration)=='R' ) {
+	    ++unit->HP;			// FIXME: how fast do we regenerate
+	}
+    }
+#endif
+}
+
 /**
 **	Change the unit's owner
+**
+**	@param oldplayer	Old owning player.
+**	@param newplayer	New owning player.
 */
 global void ChangeUnitOwner(Unit* unit,Player* oldplayer,Player* newplayer)
 {
@@ -821,6 +873,11 @@ global void ChangeUnitOwner(Unit* unit,Player* oldplayer,Player* newplayer)
 	}
     }
 
+    //
+    //	Must change food/gold and other.
+    //
+    UnitLost(unit);
+
 #ifdef NEW_UNIT
     //	Remove from old player table
 
@@ -845,6 +902,110 @@ global void ChangeUnitOwner(Unit* unit,Player* oldplayer,Player* newplayer)
 #endif
 
     unit->Player=newplayer;
+
+    //
+    //	Must change food/gold and other.
+    //
+    if( unit->Type->GivesOil ) {
+	DebugLevel0(__FUNCTION__":FIXME: oil platform transfer unsupported\n");
+    }
+    if( !unit->Type->Building ) {
+        newplayer->NumFoodUnits++;	// food needed
+	if( newplayer==ThisPlayer ) {
+	    MustRedraw|=RedrawResources;// update food
+	}
+    } else {
+	newplayer->NumBuildings++;
+    }
+    newplayer->UnitTypesCount[unit->Type->Type]++;
+    UpdateForNewUnit(unit,0);
+}
+
+/**
+**	Change the owner of all units of a player.
+**
+**	@param oldplayer	Old owning player.
+**	@param newplayer	New owning player.
+*/
+local void ChangePlayerOwner(Player* oldplayer,Player* newplayer)
+{
+    Unit* table[MAX_UNITS];
+    Unit* unit;
+    int i;
+    int n;
+
+    // NOTE: table is changed.
+    n=oldplayer->TotalNumUnits;
+    memcpy(table,oldplayer->Units,n*sizeof(Unit*));
+    for( i=0; i<n; i++ ) {
+	unit=table[i];
+	ChangeUnitOwner(unit,oldplayer,newplayer);
+    }
+}
+
+/**
+**	Rescue units.
+*/
+global void RescueUnits(void)
+{
+    Player* p;
+    Unit* unit;
+    Unit* table[MAX_UNITS];
+    Unit* near[MAX_UNITS];
+    int n;
+    int i;
+    int j;
+    int l;
+    static int norescue;
+
+    if( norescue ) {
+	return;
+    }
+    norescue=1;
+    for( p=Players; p<Players+NumPlayers; ++p ) {
+	if( p->Type!=PlayerRescuePassive && p->Type!=PlayerRescueActive ) {
+	    continue;
+	}
+	if( p->TotalNumUnits ) {
+	    norescue=0;
+	    // NOTE: table is changed.
+	    l=p->TotalNumUnits;
+	    memcpy(table,p->Units,l*sizeof(Unit*));
+	    for( j=0; j<l; j++ ) {
+		unit=table[j];
+		DebugLevel3("Checking %Zd\n",UnitNumber(unit));
+		// NOTE: I hope SelectUnits checks bounds?
+		n=SelectUnits(
+			unit->X-1,unit->Y-1,
+			unit->X+unit->Type->TileWidth+1,
+			unit->Y+unit->Type->TileHeight+1,near);
+		//
+		//	Look if human near the unit.
+		//
+		for( i=0; i<n; ++i ) {
+		    if( near[i]->Player->Type==PlayerHuman ) {
+			ChangeUnitOwner(unit,unit->Player,near[i]->Player);
+			// FIXME: more races?
+			if( unit->Player->Race==PlayerRaceHuman ) {
+			    PlayGameSound(GameSounds.HumanRescue.Sound
+				    ,MaxSampleVolume);
+			} else {
+			    PlayGameSound(GameSounds.OrcRescue.Sound
+				    ,MaxSampleVolume);
+			}
+			//
+			//	City center converts complete race
+			//	NOTE: I use a trick here, centers could
+			//		store gold.
+			if( unit->Type->StoresGold ) {
+			    ChangePlayerOwner(p,unit->Player);
+			}
+			break;
+		    }
+		}
+	    }
+	} 
+    }
 }
 
 /*----------------------------------------------------------------------------
@@ -2366,6 +2527,7 @@ global int ViewPointDistanceToUnit(Unit* dest) {
     return MapDistanceToUnit(x_v,y_v,dest);
 }
 
+#if 0
 /**
 **	Check if unit is an enemy.
 **
@@ -2387,6 +2549,33 @@ global int IsEnemy(const Player* player,const Unit* dest)
     }
     return 1;
 }
+#else
+/**
+**	Check if unit is an enemy.
+**
+**	@param player	The source player.
+**	@param dest	The destination unit.
+**
+**	@return		Returns true, if the destination unit is an enemy.
+*/
+global int IsEnemy(const Player* player,const Unit* dest)
+{
+    return player->Enemy&(1<<dest->Player->Player);
+}
+
+/**
+**	Check if unit is allied.
+**
+**	@param player	The source player.
+**	@param dest	The destination unit.
+**
+**	@return		Returns true, if the destination unit is allied.
+*/
+global int IsAllied(const Player* player,const Unit* dest)
+{
+    return player->Allied&(1<<dest->Player->Player);
+}
+#endif
 
 /**
 **	Can the source unit attack the destionation unit.