From 0524cdb99c3ec4b101605a4bc3372678589b9c24 Mon Sep 17 00:00:00 2001
From: cade <>
Date: Sat, 1 Jul 2000 22:38:41 +0000
Subject: [PATCH] - fixed fireball - fixed blizzard/death-and-decay - fixed
 invisibility - fixed unholy-armor - multiple duplicates messages handler

---
 src/action/action_repair.cpp    |  4 +-
 src/action/action_research.cpp  |  5 ++-
 src/action/action_spellcast.cpp |  7 +++-
 src/stratagus/oldmissile.cpp    | 31 ++++++++++++---
 src/stratagus/spells.cpp        | 37 ++++++++++++------
 src/ui/mainscr.cpp              | 68 ++++++++++++++++++++++++++-------
 src/ui/mouse.cpp                |  6 +++
 src/unit/unit.cpp               | 17 +++++++++
 8 files changed, 138 insertions(+), 37 deletions(-)

diff --git a/src/action/action_repair.cpp b/src/action/action_repair.cpp
index c1a0cef13..38f90a35d 100644
--- a/src/action/action_repair.cpp
+++ b/src/action/action_repair.cpp
@@ -99,8 +99,8 @@ local void RepairUnit(Unit* unit,Unit* goal)
     for( i=1; i<MaxCosts; ++i ) {
 	if( player->Resources[i]<costs[i] ) {
 	  SetMessage("We need resources!");
-    // FIXME: perhaps we should not animate if no resources are available.
-	    return;
+          // FIXME: perhaps we should not animate if no resources are available.
+	  return;
 	}
     }
     //
diff --git a/src/action/action_research.cpp b/src/action/action_research.cpp
index fa47c06c8..f2e184e31 100644
--- a/src/action/action_research.cpp
+++ b/src/action/action_research.cpp
@@ -45,8 +45,9 @@ global void HandleActionResearch(Unit* unit)
     if( unit->Command.Data.Research.Ticks>=upgrade->Costs[TimeCost] ) {
 
 	// FIXME: should als speak and tell ai
-	SetMessage2( unit->X, unit->Y, "%s: Upgrade complete", unit->Type->Name );
-
+	if( unit->Player==ThisPlayer ) {
+	  SetMessage2( unit->X, unit->Y, "%s: Upgrade complete", unit->Type->Name );
+	}
         UpgradeAcquire(unit->Player,upgrade);
 
 	unit->Reset=1;
diff --git a/src/action/action_spellcast.cpp b/src/action/action_spellcast.cpp
index 5a990d90c..d93ddecf7 100644
--- a/src/action/action_spellcast.cpp
+++ b/src/action/action_spellcast.cpp
@@ -136,8 +136,11 @@ global void HandleActionSpellCast(Unit* unit)
 		const SpellType* spell = SpellTypeById( unit->Command.Data.Move.SpellId );
 		if ( unit->Mana < spell->ManaCost )
 		  {
-		  SetMessage( "%s: not enough mana to cast spell: %s", 
-                              unit->Type->Name, spell->Ident );
+       	          if( unit->Player==ThisPlayer ) 
+		    {
+		    SetMessage( "%s: not enough mana to cast spell: %s", 
+                                unit->Type->Name, spell->Ident );
+		    }		
 		  repeat = 0;	      
 		  }
 		else
diff --git a/src/stratagus/oldmissile.cpp b/src/stratagus/oldmissile.cpp
index 8fb08aecb..f43ea1957 100644
--- a/src/stratagus/oldmissile.cpp
+++ b/src/stratagus/oldmissile.cpp
@@ -210,7 +210,7 @@ global MissileType MissileTypes[MissileTypeMax] = {
     "death and decay.png",
     32,32,
     { NULL },
-    MissileClassStayWithDelay,	
+    MissileClassDeathDecay,	
     1,
     },
 { MissileTypeType,
@@ -603,13 +603,15 @@ local int CalculateDamageStats(const UnitStats* attacker_stats
 	,const UnitStats* goal_stats)
 {
     int damage;
-
+    int basic_damage = attacker_stats->BasicDamage;
+    int piercing_damage = attacker_stats->PiercingDamage;
+    
     damage=-goal_stats->Armor;
-    damage+=attacker_stats->BasicDamage;
+    damage+= basic_damage;
     if( damage<0 ) {
 	damage=0;
     }
-    damage+=attacker_stats->PiercingDamage+1;	// round up
+    damage+=piercing_damage+1;	// round up
     damage/=2;
     damage*=((SyncRand()>>15)&1)+1;
     DebugLevel3Fn("Damage done %d\n",damage);
@@ -984,6 +986,7 @@ local int PointToPointMissile(Missile* missile)
     return 0;
 }
 
+local int BlizzardMissileHit = 0;
 /**
 **	Work for missile hit.
 */
@@ -1038,6 +1041,9 @@ global void MissileHit(const Missile* missile)
 	DebugLevel3Fn("Oops nothing to hit (%d,%d)?\n",x,y);
 	return;
     }
+    if ( BlizzardMissileHit && goal == missile->SourceUnit )
+      return; // blizzard cannot hit owner unit
+    BlizzardMissileHit = 0;  
     if ( missile->Damage )
       HitUnit(goal,missile->Damage); // direct damage, spells mostly
     else
@@ -1184,11 +1190,26 @@ global void MissileActions(void)
 		    missile->Frame+=4;	// FIXME: frames pro row
 		    if( (missile->Frame&127)
 			    >=VideoGraphicFrames(missile->Type->Sprite) ) {
+			//NOTE: vladi: blizzard cannot hit owner...
+			BlizzardMissileHit = 1;
 			MissileHit(missile);
 			missile->Type=MissileFree;
 		    }
 		}
 		break;
+	    
+	    case MissileClassDeathDecay:	
+	    	//NOTE: vladi: this is exact copy of MissileClassStayWithDelay
+		// but with check for blizzard-type hit (friendly fire:))
+		missile->Wait=missile->Type->Speed;
+		if( ++missile->Frame
+			==VideoGraphicFrames(missile->Type->Sprite) ) {
+		    BlizzardMissileHit = 1;
+		    MissileHit(missile);
+		    missile->Type=MissileFree;
+		}
+		break;
+	    
 	    case MissileClassWhirlwind:
 		missile->Wait=missile->Type->Speed;
 		missile->Frame++;
@@ -1199,7 +1220,7 @@ global void MissileActions(void)
 	        if ( missile->TTL < 1 || missile->TTL % 10 == 0 )
 		  PointToPointMissile(missile);
 		break;
-		
+
 	    case MissileClassStayWithDelay:
 		missile->Wait=missile->Type->Speed;
 		if( ++missile->Frame
diff --git a/src/stratagus/spells.cpp b/src/stratagus/spells.cpp
index ecfd5141b..b7746aaad 100644
--- a/src/stratagus/spells.cpp
+++ b/src/stratagus/spells.cpp
@@ -75,7 +75,7 @@ global SpellType SpellTypeTable[] = {
 { 0, "spell-healing",          4,   6,  -1, SpellActionHealing      , "healing",        NULL },
 { 0, "spell-exorcism",        10,   4,  -1, SpellActionExorcism     , "exorcism",       NULL },
 //      ---human mages---                                                ---human mages---          
-{ 0, "spell-fireball",         8, 100, 999, SpellActionFireball     , "fireball",       NULL },
+{ 0, "spell-fireball",         8, 100,1000, SpellActionFireball     , "fireball",       NULL },
 { 0, "spell-slow",            10,  50,1000, SpellActionSlow         , "slow",           NULL },
 { 0, "spell-flame-shield",     6,  80, 600, SpellActionFlameShield  , "flame shield",   NULL },
 { 0, "spell-invisibility",     6, 200,2000, SpellActionInvisibility , "invisibility",   NULL },
@@ -109,8 +109,11 @@ MissileType* missile_rune      = NULL;
 ----------------------------------------------------------------------------*/
 
 /*
-** Missile controllers should return 0 to continue 
-** or 1 to cancel the spell/missile
+** Missile controllers 
+**
+** To cancel a missile set it's TTL to 0, it will be handled right after
+** the controller call and missile will be down.
+**
 */
 
 /*
@@ -125,9 +128,10 @@ global int SpellFireballController( void* missile )
   Missile* mis = (Missile*)missile;
   
   //NOTE: vladi: TTL is used as counter for explosions
-  //      first 6 counts there are no explosions, then on each second
-  //      and at the target destination
-  if ( (mis->TTL < 993 && mis->TTL % 2 == 0) || (mis->X == mis->DX && mis->Y == mis->DY) ) // approx.
+  // explosions start at target and continue (10 tiles) beyond
+  // explosions are on each tile on the way
+    
+  if ( mis->TTL <= mis->State && mis->TTL % 2 == 0 ) // approx.
     {
     //+TileSize/2 to align gfx to baseline
     int x = mis->X + TileSizeX/2;
@@ -144,7 +148,8 @@ global int SpellFireballController( void* missile )
     for( i=0; i<n; ++i )
       HitUnit(table[i],FIREBALL_DAMAGE);
     }
-  return 0;	   
+  
+  return 0;
 };
 
 /*
@@ -486,6 +491,7 @@ global int SpellCast( int SpellId, Unit* unit, Unit* target, int x, int y )
 	   int sy = unit->Y;
 	   int dx = x;
 	   int dy = y;
+	   int dist;
 	   
 	   if ( target )
 	     {
@@ -493,15 +499,22 @@ global int SpellCast( int SpellId, Unit* unit, Unit* target, int x, int y )
 	     dy = target->Y;
 	     }
 	   
+	   dist = MapDistance( sx, sy, dx, dy );
+	   dx += ((dx - sx)*10)/dist;
+	   dy += ((dy - sy)*10)/dist;
+	   
+	   sx = sx*TileSizeX+TileSizeX/2;
+	   sy = sy*TileSizeX+TileSizeX/2;
+	   dx = dx*TileSizeX+TileSizeX/2;
+	   dy = dy*TileSizeX+TileSizeX/2;
+	   
 	   unit->Mana -= spell->ManaCost;
 
   	   PlayGameSound(SoundIdForName(spell->SoundIdent),MaxSampleVolume); \
 	   mis = MakeMissile( MissileTypeByIdent("missile-fireball"), 
-	                sx*TileSizeX+TileSizeX/2,   
-	                sy*TileSizeX+TileSizeX/2,   
-	                dx*TileSizeX+TileSizeX/2,   
-	                dy*TileSizeX+TileSizeX/2 ); 
+	                sx, sy, dx, dy ); 
 	   
+	   mis->State = spell->TTL - (dist - 1) * 2;
 	   mis->TTL = spell->TTL;
 	   mis->Controller = SpellFireballController;
 	   }   
@@ -646,7 +659,7 @@ global int SpellCast( int SpellId, Unit* unit, Unit* target, int x, int y )
 //  ---orc death knights---
     case SpellActionDeathCoil:
     	 if( (target && target->Type->Organic) || (!target) )  
-	   { //NOTE: fireball can be casted on spot
+	   { 
 	   Missile* mis;
 	   int sx = unit->X;
 	   int sy = unit->Y;
diff --git a/src/ui/mainscr.cpp b/src/ui/mainscr.cpp
index a05974d00..0cc158b79 100644
--- a/src/ui/mainscr.cpp
+++ b/src/ui/mainscr.cpp
@@ -480,7 +480,7 @@ global void DrawResources(void)
 
 // FIXME: need messages for chat!
 
-#define MESSAGES_TIMEOUT  FRAMES_PER_SECOND*2 // two seconds
+#define MESSAGES_TIMEOUT  FRAMES_PER_SECOND*5 // 5 seconds
 
 local char  MessageBuffer[40];		// message buffer
 local char* Message;			// message in map window
@@ -490,6 +490,7 @@ local int   MessageFrameTimeout;	// frame to expire message
 
 local char Messages[ MESSAGES_MAX ][64];
 local int  MessagesCount;
+local int  SameMessageCount;
 
 local char MessagesEvent[ MESSAGES_MAX ][64];
 local int  MessagesEventX[ MESSAGES_MAX ];
@@ -550,11 +551,51 @@ global void DrawMessage(void)
     }
   for ( z = 0; z < MessagesCount; z++ )
     {
-     if ( Messages[z][0] == '*' )
-       DrawText(TheUI.MapX+8,TheUI.MapY+8 + z*16,GameFont,Messages[z]+1);
-     else
-       DrawReverseText(TheUI.MapX+8,TheUI.MapY+8 + z*16,GameFont,Messages[z]);
+    DrawText(TheUI.MapX+8,TheUI.MapY+8 + z*16,GameFont,Messages[z] );
     }
+  if ( MessagesCount < 1 )
+    SameMessageCount = 0;  
+}
+
+/*
+**	Adds message to the stack
+**
+**	@param msg	Message to add.
+*/
+global void AddMessage( const char* msg )
+{
+    if ( MessagesCount == MESSAGES_MAX )
+      ShiftMessages();
+    strcpy( Messages[ MessagesCount ], msg );
+    MessagesCount++;
+}
+
+/*
+**	Check if this message repeats
+**
+**	@param msg	Message to check.
+**	@return non-zero to skip this message
+*/
+global int CheckRepeatMessage( const char* msg )
+{
+    if ( MessagesCount < 1 )
+      return 0;
+    if ( strcmp( msg, Messages[ MessagesCount-1 ] ) == 0 )
+      {
+      SameMessageCount++;
+      return 1;
+      }
+    else
+    if ( SameMessageCount > 0 )
+      {
+      char temp[128];
+      int n = SameMessageCount;
+      SameMessageCount = 0;
+      // NOTE: vladi: yep it's a tricky one, but should work fine prbably :)
+      sprintf( temp, "Last message repeated ~<%d~> times", n+1 );
+      AddMessage( temp );
+      }  
+    return 0;  
 }
 
 /**
@@ -569,10 +610,9 @@ global void SetMessage( char* fmt, ... )
     va_start( va, fmt );
     vsprintf( temp, fmt, va );
     va_end( va );
-    if ( MessagesCount == MESSAGES_MAX )
-      ShiftMessages();
-    strcpy( Messages[ MessagesCount ], temp );
-    MessagesCount++;
+    if ( CheckRepeatMessage( temp ) )
+      return;
+    AddMessage( temp );
     MustRedraw|=RedrawMessage|RedrawMap;
     MessageFrameTimeout = FrameCounter + MESSAGES_TIMEOUT;
 }
@@ -594,10 +634,10 @@ global void SetMessage2( int x, int y, char* fmt, ... )
     va_start( va, fmt );
     vsprintf( temp, fmt, va );
     va_end( va );
-    if ( MessagesCount == MESSAGES_MAX )
-      ShiftMessages();
-    strcpy( Messages[ MessagesCount ], temp );
-    MessagesCount++;
+    if ( CheckRepeatMessage( temp ) == 0 )
+      {
+      AddMessage( temp );
+      }
  
     if ( MessagesEventCount == MESSAGES_MAX )
       ShiftMessagesEvent();
@@ -667,7 +707,7 @@ global void CenterOnMessage(void)
     return;
   MapCenter( MessagesEventX[ MessagesEventIndex ],
              MessagesEventY[ MessagesEventIndex ] );
-  SetMessage( "*Event: %s", MessagesEvent[ MessagesEventIndex ] );
+  SetMessage( "~<Event: %s~>", MessagesEvent[ MessagesEventIndex ] );
   MessagesEventIndex++;
 }
 
diff --git a/src/ui/mouse.cpp b/src/ui/mouse.cpp
index 4f0ab8580..a92850bc2 100644
--- a/src/ui/mouse.cpp
+++ b/src/ui/mouse.cpp
@@ -535,6 +535,12 @@ global void UIHandleMouseMove(int x,int y)
 	}
     }
 
+    //NOTE: vladi: if unit is invisible, no cursor hint should be allowed
+    if ( UnitUnderCursor && !UnitVisible( UnitUnderCursor ) )
+      {
+      UnitUnderCursor = NULL;
+      }
+    
     //
     //	Selecting target.
     //
diff --git a/src/unit/unit.cpp b/src/unit/unit.cpp
index 12f5fb0fa..819a0e4f8 100644
--- a/src/unit/unit.cpp
+++ b/src/unit/unit.cpp
@@ -663,6 +663,12 @@ global int UnitVisible(const Unit* unit)
     unsigned m;
     MapField* mf;
 
+    if ( unit->Invisible && unit->Player != ThisPlayer )
+      {
+      //FIXME: vladi: should handle teams and shared vision
+      return 0;
+      }
+
     //
     //	Check if visible on screen
     //
@@ -705,6 +711,12 @@ global int UnitVisible(const Unit* unit)
 	}
     );
 
+    if ( unit->Invisible && unit->Player != ThisPlayer )
+      {
+      //FIXME: vladi: should handle teams and shared vision
+      return 0;
+      }
+
     //
     //	Check if visible on screen
     //
@@ -2127,6 +2139,11 @@ global void HitUnit(Unit* unit,int damage)
 
     DebugCheck( damage==0 || unit->HP==0 || unit->Type->Vanishes );
 
+    if ( unit->UnholyArmor > 0 )
+      { //NOTE: vladi: units with active UnholyArmour are invulnerable
+      return;
+      }
+    
     type=unit->Type;
     if( !unit->Attacked ) {
 	if( unit->Player==ThisPlayer ) {