nemo's great patch:
authorunc0rr
Thu, 30 Apr 2009 20:13:44 +0000
changeset 2017 7845c77c8d31
parent 2016 73b0bcc4396d
child 2018 aec48276cced
nemo's great patch: - Speech bubbles - Vampirism + karma
QTfrontend/ammoSchemeModel.cpp
QTfrontend/gamecfgwidget.cpp
QTfrontend/hedgewars.qrc
QTfrontend/hwconsts.cpp.in
QTfrontend/pages.cpp
QTfrontend/pages.h
hedgewars/CCHandlers.inc
hedgewars/GSHandlers.inc
hedgewars/HHHandlers.inc
hedgewars/SDLh.pas
hedgewars/uAIAmmoTests.pas
hedgewars/uAmmos.pas
hedgewars/uChat.pas
hedgewars/uConsole.pas
hedgewars/uConsts.pas
hedgewars/uGears.pas
hedgewars/uIO.pas
hedgewars/uLocale.pas
hedgewars/uMisc.pas
hedgewars/uStore.pas
hedgewars/uTeams.pas
share/hedgewars/Data/Locale/bg.txt
share/hedgewars/Data/Locale/cs.txt
share/hedgewars/Data/Locale/de.txt
share/hedgewars/Data/Locale/en.txt
share/hedgewars/Data/Locale/es.txt
share/hedgewars/Data/Locale/fi.txt
share/hedgewars/Data/Locale/fr.txt
share/hedgewars/Data/Locale/it.txt
share/hedgewars/Data/Locale/pl.txt
share/hedgewars/Data/Locale/pt-br.txt
share/hedgewars/Data/Locale/ru.txt
share/hedgewars/Data/Locale/sk.txt
share/hedgewars/Data/Locale/tr.txt
share/hedgewars/Data/Locale/uk.txt
share/hedgewars/Data/Locale/zh_CN.txt
share/hedgewars/Data/Locale/zh_TW.txt
--- a/QTfrontend/ammoSchemeModel.cpp	Sun Apr 26 15:47:03 2009 +0000
+++ b/QTfrontend/ammoSchemeModel.cpp	Thu Apr 30 20:13:44 2009 +0000
@@ -32,11 +32,13 @@
 		<< QVariant(false)         // laser sight    6
 		<< QVariant(false)         // invulnerable   7
 		<< QVariant(true)          // add mines      8
-		<< QVariant(100)           // damage modfier 9
-		<< QVariant(45)            // turn time      10
-		<< QVariant(100)           // init health    11
-		<< QVariant(15)            // sudden death   12
-		<< QVariant(5)             // case prob      13
+		<< QVariant(false)         // vampiric       9
+		<< QVariant(false)         // karma          10
+		<< QVariant(100)           // damage modfier 11
+		<< QVariant(45)            // turn time      12
+		<< QVariant(100)           // init health    13
+		<< QVariant(15)            // sudden death   14
+		<< QVariant(5)             // case prob      15
 		;
 
 AmmoSchemeModel::AmmoSchemeModel(QObject* parent, const QString & fileName) :
@@ -61,11 +63,13 @@
 		<< "laser"            //  6
 		<< "invulnerability"  //  7
 		<< "mines"            //  8
-		<< "damagefactor"     //  9
-		<< "turntime"         // 10
-		<< "health"           // 11
-		<< "suddendeath"      // 12
-		<< "caseprobability"  // 13
+		<< "vampiric"         //  9
+		<< "karma"            // 10
+		<< "damagefactor"     // 11
+		<< "turntime"         // 12
+		<< "health"           // 13
+		<< "suddendeath"      // 14
+		<< "caseprobability"  // 15
 		;
 
 	QList<QVariant> proMode;
@@ -79,11 +83,13 @@
 		<< QVariant(false)         // laser sight    6
 		<< QVariant(false)         // invulnerable   7
 		<< QVariant(false)         // add mines      8
-		<< QVariant(100)           // damage modfier 9
-		<< QVariant(15)            // turn time      10
-		<< QVariant(100)           // init health    11
-		<< QVariant(15)            // sudden death   12
-		<< QVariant(0)             // case prob      13
+		<< QVariant(false)         // vampiric       9
+		<< QVariant(false)         // karma          10
+		<< QVariant(100)           // damage modfier 11
+		<< QVariant(15)            // turn time      12
+		<< QVariant(100)           // init health    13
+		<< QVariant(15)            // sudden death   14
+		<< QVariant(0)             // case prob      15
 		;
 
 	QList<QVariant> shoppa;
@@ -97,11 +103,13 @@
 		<< QVariant(false)         // laser sight    6
 		<< QVariant(false)         // invulnerable   7
 		<< QVariant(false)         // add mines      8
-		<< QVariant(100)           // damage modfier 9
-		<< QVariant(30)            // turn time      10
-		<< QVariant(100)           // init health    11
-		<< QVariant(50)            // sudden death   12
-		<< QVariant(1)             // case prob      13
+		<< QVariant(false)         // vampiric       9
+		<< QVariant(false)         // karma          10
+		<< QVariant(100)           // damage modfier 11
+		<< QVariant(30)            // turn time      12
+		<< QVariant(100)           // init health    13
+		<< QVariant(50)            // sudden death   14
+		<< QVariant(1)             // case prob      15
 		;
 
 	QList<QVariant> basketball;
@@ -115,11 +123,13 @@
 		<< QVariant(false)         // laser sight    6
 		<< QVariant(true)          // invulnerable   7
 		<< QVariant(false)         // add mines      8
-		<< QVariant(100)           // damage modfier 9
-		<< QVariant(30)            // turn time      10
-		<< QVariant(100)           // init health    11
-		<< QVariant(15)            // sudden death   12
-		<< QVariant(0)             // case prob      13
+		<< QVariant(false)         // vampiric       9
+		<< QVariant(false)         // karma          10
+		<< QVariant(100)           // damage modfier 11
+		<< QVariant(30)            // turn time      12
+		<< QVariant(100)           // init health    13
+		<< QVariant(15)            // sudden death   14
+		<< QVariant(0)             // case prob      15
 		;
 
 	schemes.append(defaultScheme);
--- a/QTfrontend/gamecfgwidget.cpp	Sun Apr 26 15:47:03 2009 +0000
+++ b/QTfrontend/gamecfgwidget.cpp	Thu Apr 30 20:13:44 2009 +0000
@@ -107,13 +107,17 @@
 		result |= 0x80;
 	if (schemeData(8).toBool())
 		result |= 0x100;
+	if (schemeData(9).toBool())
+		result |= 0x200;
+	if (schemeData(10).toBool())
+		result |= 0x400;
 
 	return result;
 }
 
 quint32 GameCFGWidget::getInitHealth() const
 {
-	return schemeData(11).toInt();
+	return schemeData(13).toInt();
 }
 
 QStringList GameCFGWidget::getFullConfig() const
@@ -121,10 +125,10 @@
 	QStringList sl;
 	sl.append("eseed " + pMapContainer->getCurrentSeed());
 	sl.append(QString("e$gmflags %1").arg(getGameFlags()));
-	sl.append(QString("e$damagepct %1").arg(schemeData(9).toInt()));
-	sl.append(QString("e$turntime %1").arg(schemeData(10).toInt() * 1000));
-	sl.append(QString("e$sd_turns %1").arg(schemeData(12).toInt()));
-	sl.append(QString("e$casefreq %1").arg(schemeData(13).toInt()));
+	sl.append(QString("e$damagepct %1").arg(schemeData(11).toInt()));
+	sl.append(QString("e$turntime %1").arg(schemeData(12).toInt() * 1000));
+	sl.append(QString("e$sd_turns %1").arg(schemeData(14).toInt()));
+	sl.append(QString("e$casefreq %1").arg(schemeData(15).toInt()));
 	sl.append(QString("e$template_filter %1").arg(pMapContainer->getTemplateFilter()));
 
 	QString currentMap = pMapContainer->getCurrentMap();
--- a/QTfrontend/hedgewars.qrc	Sun Apr 26 15:47:03 2009 +0000
+++ b/QTfrontend/hedgewars.qrc	Thu Apr 30 20:13:44 2009 +0000
@@ -53,6 +53,8 @@
     <file>res/btnMines.png</file>
     <file>res/btnTeamsDivide.png</file>
     <file>res/btnSolid.png</file>
+    <file>res/btnVampiric.png</file>
+    <file>res/btnKarma.png</file>
     <file>res/iconBox.png</file>
     <file>res/iconHealth.png</file>
     <file>res/iconSuddenDeath.png</file>
--- a/QTfrontend/hwconsts.cpp.in	Sun Apr 26 15:47:03 2009 +0000
+++ b/QTfrontend/hwconsts.cpp.in	Thu Apr 30 20:13:44 2009 +0000
@@ -29,14 +29,14 @@
 QStringList * Themes;
 QStringList * mapList;
 
-QString * cDefaultAmmoStore = new QString("939192942219912103223511100120100000");
+QString * cDefaultAmmoStore = new QString("9391929422199121032235111001201000001");
 QList< QPair<QString, QString> > cDefaultAmmos =
 	QList< QPair<QString, QString> >()
 	<< qMakePair(QString("Default"), *cDefaultAmmoStore)
-	<< qMakePair(QString("Crazy"),     QString("999999999999999999299999999999999929"))
-	<< qMakePair(QString("Pro mode"),  QString("909000900000000000000900000000000000"))
-	<< qMakePair(QString("Shoppa"),    QString("000000990000000000000000000000000000"))
-	<< qMakePair(QString("Basketball"),QString("000000900000090000000000000000000000"))
+	<< qMakePair(QString("Crazy"),     QString("9999999999999999992999999999999999299"))
+	<< qMakePair(QString("Pro mode"),  QString("9090009000000000000009000000000000000"))
+	<< qMakePair(QString("Shoppa"),    QString("0000009900000000000000000000000000000"))
+	<< qMakePair(QString("Basketball"),QString("0000009000000900000000000000000000000"))
 	;
 
 QColor * color1 = new QColor(221,   0,   0);
--- a/QTfrontend/pages.cpp	Sun Apr 26 15:47:03 2009 +0000
+++ b/QTfrontend/pages.cpp	Thu Apr 30 20:13:44 2009 +0000
@@ -916,6 +916,14 @@
 	TBW_mines->setText(ToggleButtonWidget::tr("Add Mines"));
 	glGMLayout->addWidget(TBW_mines,1,3,1,1);
 
+	TBW_vampiric = new ToggleButtonWidget(gbGameModes, ":/res/btnVampiric.png");
+	TBW_vampiric->setText(ToggleButtonWidget::tr("Vampirism"));
+	glGMLayout->addWidget(TBW_vampiric,2,0,1,1);
+
+	TBW_karma = new ToggleButtonWidget(gbGameModes, ":/res/btnKarma.png");
+	TBW_karma->setText(ToggleButtonWidget::tr("Karma"));
+	glGMLayout->addWidget(TBW_karma,2,1,1,1);
+
 	// Right
 	QLabel * l;
 	
@@ -1031,11 +1039,13 @@
 	mapper->addMapping(TBW_laserSight->button(), 6);
 	mapper->addMapping(TBW_invulnerable->button(), 7);
 	mapper->addMapping(TBW_mines->button(), 8);
-	mapper->addMapping(SB_DamageModifier, 9);
-	mapper->addMapping(SB_TurnTime, 10);
-	mapper->addMapping(SB_InitHealth, 11);
-	mapper->addMapping(SB_SuddenDeath, 12);
-	mapper->addMapping(SB_CaseProb, 13);
+	mapper->addMapping(TBW_vampiric->button(), 9);
+	mapper->addMapping(TBW_karma->button(), 10);
+	mapper->addMapping(SB_DamageModifier, 11);
+	mapper->addMapping(SB_TurnTime, 12);
+	mapper->addMapping(SB_InitHealth, 13);
+	mapper->addMapping(SB_SuddenDeath, 14);
+	mapper->addMapping(SB_CaseProb, 15);
 
 	mapper->toFirst();
 }
--- a/QTfrontend/pages.h	Sun Apr 26 15:47:03 2009 +0000
+++ b/QTfrontend/pages.h	Thu Apr 30 20:13:44 2009 +0000
@@ -424,6 +424,8 @@
 	ToggleButtonWidget * TBW_laserSight;
 	ToggleButtonWidget * TBW_invulnerable;
 	ToggleButtonWidget * TBW_mines;
+	ToggleButtonWidget * TBW_vampiric;
+	ToggleButtonWidget * TBW_karma;
 
 	QSpinBox * SB_DamageModifier;
 	QSpinBox * SB_TurnTime;
--- a/hedgewars/CCHandlers.inc	Sun Apr 26 15:47:03 2009 +0000
+++ b/hedgewars/CCHandlers.inc	Thu Apr 30 20:13:44 2009 +0000
@@ -390,6 +390,35 @@
      end
 end;
 
+procedure chHogSay(var s: shortstring);
+var Gear: PGear;
+    text: shortstring;
+begin
+text:= copy(s, 2, Length(s)-1);
+if CheckNoTeamOrHH or ((CurrentHedgehog^.Gear^.State and gstHHDriven) = 0) then
+    begin
+    chSay(text);
+    exit
+    end;
+
+if not CurrentTeam^.ExtDriven then SendIPC('h' + s);
+if byte(s[1]) < 4 then
+    begin
+    Gear:= AddGear(0, 0, gtSpeechBubble, 0, _0, _0, 0);
+    Gear^.Text:= text;
+    Gear^.Hedgehog:= CurrentHedgehog;
+    Gear^.State:= byte(s[1]);
+    end
+else
+    begin
+    // If I knew how to add a gear without it becoming immediately active, I'd
+    // just create/attach the hedgehog SpeechGear here, then activate it where
+    // SpeechType/SpeechText are activated
+    SpeechType:= byte(s[1]);
+    SpeechText:= text
+    end;
+end;
+
 procedure chNewGrave;
 begin
 if CheckNoTeamOrHH then exit;
--- a/hedgewars/GSHandlers.inc	Sun Apr 26 15:47:03 2009 +0000
+++ b/hedgewars/GSHandlers.inc	Thu Apr 30 20:13:44 2009 +0000
@@ -67,8 +67,7 @@
 		PlaySound(sndOw1, false, PHedgehog(Gear^.Hedgehog)^.Team^.voicepack);
 
 	dmg:= modifyDamage(1 + hwRound((hwAbs(Gear^.dY) - _0_4) * 70));
-	inc(Gear^.Damage, dmg);
-	AddDamageTag(hwRound(Gear^.X), hwRound(Gear^.Y) + cHHRadius, dmg, PHedgehog(Gear^.Hedgehog)^.Team^.Clan^.Color);
+    ApplyDamage(Gear, dmg);
 	end
 end;
 
@@ -255,7 +254,7 @@
 
 if Gear^.Timer = 0 then
 	begin
-	if Gear^.Kind = gtHealthTag then
+	if (Gear^.Kind = gtHealthTag) and (PHedgehog(Gear^.Hedgehog)^.Gear <> nil) then
 		PHedgehog(Gear^.Hedgehog)^.Gear^.Active:= true; // to let current hh die
 	DeleteGear(Gear)
 	end
@@ -288,6 +287,44 @@
 Gear^.Y:= Gear^.Y - int2hwFloat(Gear^.Tex^.h)
 end;
 
+procedure doStepSpeechBubbleWork(Gear: PGear);
+begin
+dec(Gear^.Timer);
+
+if (PHedgehog(Gear^.Hedgehog)^.Gear <> nil) then
+    begin
+    Gear^.X:= PHedgehog(Gear^.Hedgehog)^.Gear^.X+int2hwFloat(Gear^.Tex^.w div 2  - Gear^.State);
+    Gear^.Y:= PHedgehog(Gear^.Hedgehog)^.Gear^.Y-int2hwFloat(16+Gear^.Tex^.h);
+    end;
+
+if Gear^.Timer = 0 then
+    begin
+    CurrentHedgehog^.SpeechGear:= nil;
+	DeleteGear(Gear)
+    end;
+end;
+
+procedure doStepSpeechBubble(Gear: PGear);
+begin
+if (CurrentHedgehog^.SpeechGear <> nil) then DeleteGear(CurrentHedgehog^.SpeechGear);
+CurrentHedgehog^.SpeechGear:= Gear;
+
+Gear^.Timer:= max(Length(Gear^.Text)*150,3000);
+
+Gear^.Tex:= RenderSpeechBubbleTex(Gear^.Text, Gear^.State, fnt16);
+
+// Arbitrary offsets added to the widths based on shape of current tails
+case Gear^.State of
+    1: Gear^.State:= SpritesData[sprSpeechTail].Width-28;
+    2: Gear^.State:= SpritesData[sprThoughtTail].Width-20;
+    3: Gear^.State:= SpritesData[sprShoutTail].Width-10;
+    end;
+
+Gear^.doStep:= @doStepSpeechBubbleWork;
+
+Gear^.Y:= Gear^.Y - int2hwFloat(Gear^.Tex^.h)
+end;
+
 ////////////////////////////////////////////////////////////////////////////////
 procedure doStepGrave(Gear: PGear);
 begin
--- a/hedgewars/HHHandlers.inc	Sun Apr 26 15:47:03 2009 +0000
+++ b/hedgewars/HHHandlers.inc	Thu Apr 30 20:13:44 2009 +0000
@@ -100,6 +100,7 @@
 
 procedure Attack(Gear: PGear);
 var xx, yy: hwFloat;
+    tmpGear: PGear;
 begin
 with Gear^,
      PHedgehog(Gear^.Hedgehog)^ do
@@ -189,10 +190,20 @@
                       amInvulnerable: Invulnerable:= true;
                       amExtraTime: TurnTimeLeft:= TurnTimeLeft + 30000;
                       amLaserSight: cLaserSighting:= true;
+                      amVampiric: cVampiric:= true;
                   end;
 
         uStats.AmmoUsed(Ammo^[CurSlot, CurAmmo].AmmoType);
 
+        if not (SpeechText = '') then
+            begin
+            tmpGear:= AddGear(0, 0, gtSpeechBubble, 0, _0, _0, 0);
+            tmpGear^.Text:= SpeechText;
+            tmpGear^.Hedgehog:= CurrentHedgehog;
+            tmpGear^.State:= SpeechType;
+            SpeechText:= ''
+            end;
+
         Power:= 0;
         if (CurAmmoGear <> nil)
            and (((Ammo^[CurSlot, CurAmmo].Propz) and ammoprop_AltUse) = 0){check for dropping ammo from rope} then
@@ -386,6 +397,7 @@
          or TestCollisionYwithGear(Gear, -1)) then Gear^.Y:= Gear^.Y - _1;
       end;
 
+   // ARTILLERY HERE
    if not TestCollisionXwithGear(Gear, hwSign(Gear^.dX)) then Gear^.X:= Gear^.X + SignAs(_1, Gear^.dX);
    SetAllHHToActive;
 
@@ -504,6 +516,7 @@
 if (Gear^.State and gstMoving) <> 0 then
    begin
    Gear^.State:= Gear^.State and not gstAnimation;
+// ARTILLERY but not being moved by explosions
    Gear^.X:= Gear^.X + Gear^.dX;
    Gear^.Y:= Gear^.Y + Gear^.dY;
    if (not Gear^.dY.isNegative) and
--- a/hedgewars/SDLh.pas	Sun Apr 26 15:47:03 2009 +0000
+++ b/hedgewars/SDLh.pas	Thu Apr 30 20:13:44 2009 +0000
@@ -73,6 +73,7 @@
       SDL_HWACCEL     = $00000100;
       SDL_SRCCOLORKEY = $00001000;
       SDL_RLEACCEL    = $00004000;
+      SDL_SRCALPHA    = $00010000;
 
       SDL_NOEVENT     = 0;
       SDL_ACTIVEEVENT = 1;
@@ -247,6 +248,7 @@
 function  SDL_CreateRGBSurfaceFrom(pixels: Pointer; width, height, depth, pitch: LongInt; RMask, GMask, BMask, AMask: Longword): PSDL_Surface; cdecl; external SDLLibName;
 procedure SDL_FreeSurface(Surface: PSDL_Surface); cdecl; external SDLLibName;
 function  SDL_SetColorKey(surface: PSDL_Surface; flag, key: Longword): LongInt; cdecl; external SDLLibName;
+function  SDL_SetAlpha(surface: PSDL_Surface; flag, key: Longword): LongInt; cdecl; external SDLLibName;
 
 function  SDL_UpperBlit(src: PSDL_Surface; srcrect: PSDL_Rect; dst: PSDL_Surface; dstrect: PSDL_Rect): LongInt; cdecl; external SDLLibName;
 function  SDL_FillRect(dst: PSDL_Surface; dstrect: PSDL_Rect; color: Longword): LongInt; cdecl; external SDLLibName;
--- a/hedgewars/uAIAmmoTests.pas	Sun Apr 26 15:47:03 2009 +0000
+++ b/hedgewars/uAIAmmoTests.pas	Thu Apr 30 20:13:44 2009 +0000
@@ -73,14 +73,15 @@
 			(proc: nil;              flags: 0), // amBanana
 			(proc: nil;              flags: 0), // amHellishBomb
 			(proc: nil;              flags: 0), // amNapalm
-			(proc: nil;              flags: 0),  // amDrill
-			(proc: nil;              flags: 0),  // amBallgun
+			(proc: nil;              flags: 0), // amDrill
+			(proc: nil;              flags: 0), // amBallgun
 			(proc: nil;              flags: 0), // amRCPlane
 			(proc: nil;              flags: 0), // amLowGravity
 			(proc: nil;              flags: 0), // amExtraDamage
 			(proc: nil;              flags: 0), // amInvulnerable
 			(proc: nil;              flags: 0), // amExtraTime
-			(proc: nil;              flags: 0) // amLaserSight
+			(proc: nil;              flags: 0), // amLaserSight
+			(proc: nil;              flags: 0)  // amVampiric
 			);
 
 const BadTurn = Low(LongInt) div 4;
--- a/hedgewars/uAmmos.pas	Sun Apr 26 15:47:03 2009 +0000
+++ b/hedgewars/uAmmos.pas	Thu Apr 30 20:13:44 2009 +0000
@@ -61,12 +61,17 @@
 var cnt: Longword;
     a: TAmmoType;
     ammos: TAmmoCounts;
+    substr: shortstring; // TEMPORARY
 begin
 TryDo(byte(s[0]) = byte(ord(High(TAmmoType)) + 1), 'Invalid ammo scheme (incompatible frontend)', true);
 
 // TEMPORARY hardcoded check on shoppa pending creation of probability editor
-if (s = '000000990000009000000000000000000000') or (s = '000000990000000000000000000000000000') then
+substr:= Copy(s,1,15);
+if (substr = '000000990000009') or 
+   (substr = '000000990000000') then
     shoppa:= true;
+for a:= Low(TAmmoType) to High(TAmmoType) do
+    if (ord(a) > 14) and (s[ord(a)+1] <> '0') then shoppa:= false;  // TEMPORARY etc - this just avoids updating every time new wep is added
 inc(StoreCnt);
 TryDo(StoreCnt <= cMaxHHs, 'Ammo stores overflow', true);
 
@@ -82,7 +87,8 @@
         end;
     if ((a = amLowGravity) and ((GameFlags and gfLowGravity) <> 0)) or
        ((a = amInvulnerable) and ((GameFlags and gfInvulnerable) <> 0)) or
-       ((a = amLaserSight) and ((GameFlags and gfLaserSight) <> 0)) then
+       ((a = amLaserSight) and ((GameFlags and gfLaserSight) <> 0)) or
+       ((a = amVampiric) and ((GameFlags and gfVampiric) <> 0)) then
         begin
         cnt:= 0;
         Ammoz[a].Probability:= 0 
--- a/hedgewars/uChat.pas	Sun Apr 26 15:47:03 2009 +0000
+++ b/hedgewars/uChat.pas	Thu Apr 30 20:13:44 2009 +0000
@@ -132,6 +132,40 @@
 procedure AcceptChatString(s: shortstring);
 var i: TWave;
 begin
+// "Make hedgehog say something"
+if (s[1] = '"') and (s[Length(s)] = '"') then 
+    begin
+    ParseCommand('/hogsay '+#1+copy(s, 2, Length(s)-2), true);
+    exit
+    end;
+// 'Make hedgehog think something'
+if (s[1] = '''') and (s[Length(s)] = '''') then 
+    begin
+    ParseCommand('/hogsay '+#2+copy(s, 2, Length(s)-2), true);
+    exit
+    end;
+// -Make hedgehog yell something-
+if (s[1] = '-') and (s[Length(s)] = '-') then 
+    begin
+    ParseCommand('/hogsay '+#3+copy(s, 2, Length(s)-2), true);
+    exit
+    end;
+// These 3 are same as above, only are to make the hedgehog say it on next attack
+if (s[1] = '/') and (copy(s, 1, 5) = '/hsa ') then
+    begin
+    ParseCommand('/hogsay '+#4+copy(s, 6, Length(s)-5), true);
+    exit
+    end;
+if (s[1] = '/') and (copy(s, 1, 5) = '/hta ') then
+    begin
+    ParseCommand('/hogsay '+#5+copy(s, 6, Length(s)-5), true);
+    exit
+    end;
+if (s[1] = '/') and (copy(s, 1, 5) = '/hya ') then
+    begin
+    ParseCommand('/hogsay '+#6+copy(s, 6, Length(s)-5), true);
+    exit
+    end;
 if (s[1] = '/') and (copy(s, 1, 4) <> '/me ') then
 	begin
 	if CurrentTeam^.ExtDriven then exit;
@@ -143,8 +177,11 @@
 			exit
 			end;
 	if (s = '/newgrave') then
+        begin
 	    ParseCommand('/newgrave', true);
-	end
+        exit
+        end;
+    end
 	else
 		ParseCommand('/say ' + s, true);
 end;
--- a/hedgewars/uConsole.pas	Sun Apr 26 15:47:03 2009 +0000
+++ b/hedgewars/uConsole.pas	Thu Apr 30 20:13:44 2009 +0000
@@ -262,6 +262,7 @@
 RegisterVariable('chat'    , vtCommand, @chChat         , true );
 RegisterVariable('newgrave', vtCommand, @chNewGrave     , false);
 RegisterVariable('say'     , vtCommand, @chSay          , true );
+RegisterVariable('hogsay'  , vtCommand, @chHogSay       , true );
 RegisterVariable('ammomenu', vtCommand, @chAmmoMenu     , false);
 RegisterVariable('+precise', vtCommand, @chPrecise_p    , false);
 RegisterVariable('-precise', vtCommand, @chPrecise_m    , false);
--- a/hedgewars/uConsts.pas	Sun Apr 26 15:47:03 2009 +0000
+++ b/hedgewars/uConsts.pas	Thu Apr 30 20:13:44 2009 +0000
@@ -59,7 +59,10 @@
 			sprCakeWalk, sprCakeDown, sprAMAmmosBW, sprWatermelon,
 			sprEvilTrace, sprHellishBomb, sprSeduction, sprDress,
 			sprCensored, sprDrill, sprHandDrill, sprHandBallgun, sprBalls,
-			sprPlane, sprHandPlane, sprUtility, sprInvulnerable, sprGirder);
+			sprPlane, sprHandPlane, sprUtility, sprInvulnerable, sprVampiric, sprGirder, 
+            sprSpeechCorner, sprSpeechEdge, sprSpeechTail, 
+            sprThoughtCorner, sprThoughtEdge, sprThoughtTail, 
+            sprShoutCorner, sprShoutEdge, sprShoutTail);
 
 	TGearType = (gtAmmo_Bomb, gtHedgehog, gtAmmo_Grenade, gtHealthTag, // 3
 			gtGrave, gtUFO, gtShotgunShot, gtPickHammer, gtRope, // 8
@@ -69,7 +72,7 @@
 			gtParachute, gtAirAttack, gtAirBomb, gtBlowTorch, gtGirder, // 27
 			gtTeleport, gtSwitcher, gtTarget, gtMortar, // 31
 			gtWhip, gtKamikaze, gtCake, gtSeduction, gtWatermelon, gtMelonPiece, // 37
-			gtHellishBomb, gtEvilTrace, gtWaterUp, gtDrill, gtBallGun, gtBall,gtRCPlane);
+			gtHellishBomb, gtEvilTrace, gtWaterUp, gtDrill, gtBallGun, gtBall,gtRCPlane, gtSpeechBubble);
 
 	TVisualGearType = (vgtFlake, vgtCloud, vgtExplPart, vgtExplPart2, vgtFire,
 			vgtSmallDamageTag, vgtTeamHealthSorter);
@@ -93,7 +96,7 @@
 			amBaseballBat, amParachute, amAirAttack, amMineStrike, amBlowTorch,
 			amGirder, amTeleport, amSwitch, amMortar, amKamikaze, amCake,
 			amSeduction, amWatermelon, amHellishBomb, amNapalm, amDrill, amBallgun, 
-            amRCPlane, amLowGravity, amExtraDamage, amInvulnerable, amExtraTime, amLaserSight);
+            amRCPlane, amLowGravity, amExtraDamage, amInvulnerable, amExtraTime, amLaserSight, amVampiric);
 
 	THWFont = (fnt16, fntBig, fntSmall);
 
@@ -203,6 +206,8 @@
 	gfLaserSight   = $00000040;
 	gfInvulnerable = $00000080;
 	gfMines        = $00000100;
+	gfVampiric     = $00000200;
+	gfKarma        = $00000400;
 	gfOneClanMode  = $10000000;
 
 	gstDrowning       = $00000001;
@@ -497,8 +502,28 @@
 			Width:  48; Height: 48; saveSurf: false), // sprUtility
 			(FileName:'Invulnerable';Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
 			Width:  48; Height: 48; saveSurf: false), // sprInvulnerable
+			(FileName:'Vampiric';Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
+			Width:  48; Height: 48; saveSurf: false), // sprVampiric
 			(FileName:    'amGirder'; Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
-			Width: 512; Height:512; saveSurf: false) // sprGirder
+			Width: 512; Height:512; saveSurf: false), // sprGirder
+			(FileName:'SpeechCorner';Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
+			Width:  12; Height: 9; saveSurf: true), // sprSpeechCorner
+			(FileName:'SpeechEdge';Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
+			Width:  25; Height: 9; saveSurf: true), // sprSpeechEdge
+			(FileName:'SpeechTail';Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
+			Width:  25; Height: 26; saveSurf: true), // sprSpeechTail
+			(FileName:'ThoughtCorner';Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
+			Width:  49; Height: 37; saveSurf: true), // sprThoughtCorner
+			(FileName:'ThoughtEdge';Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
+			Width:  23; Height: 16; saveSurf: true), // sprThoughtEdge
+			(FileName:'ThoughtTail';Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
+			Width:  45; Height: 65; saveSurf: true), // sprThoughtTail
+			(FileName:'ShoutCorner';Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
+			Width:  34; Height: 23; saveSurf: true), // sprShoutCorner
+			(FileName:'ShoutEdge';Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
+			Width:  30; Height: 20; saveSurf: true), // sprShoutEdge
+			(FileName:'ShoutTail';Path: ptHedgehog; AltPath: ptNone; Texture: nil; Surface: nil;
+			Width:  30; Height: 37; saveSurf: true) // sprShoutTail
 			);
 
 	Wavez: array [TWave] of record
@@ -1300,6 +1325,27 @@
 			isDamaging: false;
 			SkipTurns: 0;
 			PosCount: 1;
+			PosSprite: sprWater),
+			(NameId: sidVampiric;
+			NameTex: nil;
+			Probability: 15;
+			NumberInCase: 1;
+			Ammo: (Propz: ammoprop_NoCrosshair or
+						  ammoprop_DontHold or
+						  ammoprop_AltUse or
+                          ammoprop_Utility;
+					Count: 1;
+					NumPerTurn: 0;
+					Timer: 0;
+					Pos: 0;
+					AmmoType: amVampiric);
+			Slot: 6;
+			TimeAfterTurn: 0;
+			minAngle: 0;
+			maxAngle: 0;
+			isDamaging: false;
+			SkipTurns: 0;
+			PosCount: 1;
 			PosSprite: sprWater)
 			);
 
--- a/hedgewars/uGears.pas	Sun Apr 26 15:47:03 2009 +0000
+++ b/hedgewars/uGears.pas	Thu Apr 30 20:13:44 2009 +0000
@@ -54,11 +54,13 @@
 			IntersectGear: PGear;
 			TriggerId: Longword;
 			uid: Longword;
+            Text: shortstring;
 			end;
 
 function  AddGear(X, Y: LongInt; Kind: TGearType; State: Longword; dX, dY: hwFloat; Timer: LongWord): PGear;
 procedure ProcessGears;
 procedure ResetUtilities;
+procedure ApplyDamage(Gear: PGear; Damage: Longword);
 procedure SetAllToActive;
 procedure SetAllHHToActive;
 procedure DrawGears;
@@ -71,6 +73,9 @@
 var CurAmmoGear: PGear = nil;
     GearsList: PGear = nil;
     KilledHHs: Longword = 0;
+    SuddenDeathDmg: Boolean = false;
+    SpeechType: Longword = 1;
+    SpeechText: shortstring;
 
 implementation
 uses uWorld, uMisc, uStore, uConsole, uSound, uTeams, uRandom, uCollisions,
@@ -157,7 +162,8 @@
 			@doStepDrill,
 			@doStepBallgun,
 			@doStepBomb,
-			@doStepRCPlane
+			@doStepRCPlane,
+			@doStepSpeechBubble
 			);
 
 procedure InsertGearToList(Gear: PGear);
@@ -248,7 +254,10 @@
                 end;
    gtHealthTag: begin
                 Result^.Timer:= 1500;
-                Result^.Z:= 2001;
+                Result^.Z:= 2002;
+                end;
+   gtSpeechBubble: begin
+                Result^.Z:= 2003;
                 end;
        gtGrave: begin
                 Result^.Radius:= 10;
@@ -440,9 +449,6 @@
 		if (Gear^.Damage <> 0) and
 		(not Gear^.Invulnerable) then
 			begin
-            if (PHedgehog(Gear^.Hedgehog)^.Team = CurrentTeam) and
-               (Gear^.Damage <> int(cHealthDecrease)) then
-                Gear^.State:= Gear^.State or gstLoser;
 			CheckNoDamage:= false;
 			uStats.HedgehogDamaged(Gear);
 			dmg:= Gear^.Damage;
@@ -451,6 +457,10 @@
 			else
 				dec(Gear^.Health, dmg);
 
+            if (PHedgehog(Gear^.Hedgehog)^.Team = CurrentTeam) and
+                not SuddenDeathDmg then
+                Gear^.State:= Gear^.State or gstLoser;
+
 			AddGear(hwRound(Gear^.X), hwRound(Gear^.Y) - cHHRadius - 12,
 					gtHealthTag, dmg, _0, _0, 0)^.Hedgehog:= Gear^.Hedgehog;
 
@@ -462,6 +472,7 @@
 		end;
 	Gear:= Gear^.NextGear
 	end;
+SuddenDeathDmg:= false;
 end;
 
 procedure HealthMachine;
@@ -562,6 +573,7 @@
 			else begin
 				bBetweenTurns:= true;
 				HealthMachine;
+                SuddenDeathDmg:= true;
 				step:= stChDmg
 				end
 			end;
@@ -612,9 +624,14 @@
 procedure ResetUtilities;
 var  i: LongInt;
 begin
+    SpeechText:= ''; // in case it hasn't been consumed
+
     if (GameFlags and gfLowGravity) = 0 then
         cGravity:= cMaxWindSpeed;
 
+    if (GameFlags and gfVampiric) = 0 then
+        cVampiric:= false;
+
     cDamageModifier:= _1;
 
     if (GameFlags and gfLaserSight) = 0 then
@@ -629,6 +646,50 @@
                      if (Gear <> nil) then
                          Gear^.Invulnerable:= false;
 end;
+procedure ApplyDamage(Gear: PGear; Damage: Longword);
+var s: shortstring;
+    vampDmg: Longword;
+begin
+	inc(Gear^.Damage, Damage);
+	if Gear^.Kind = gtHedgehog then
+    begin
+	AddDamageTag(hwRound(Gear^.X), hwRound(Gear^.Y), Damage, PHedgehog(Gear^.Hedgehog)^.Team^.Clan^.Color);
+    Damage:= min(Damage, Gear^.Health);
+    if (Gear <> CurrentHedgehog^.Gear) and (CurrentHedgehog^.Gear <> nil) and (Damage >= 1) then
+        begin
+        if cVampiric then
+            begin
+            vampDmg:= hwRound(int2hwFloat(Damage)*_0_8);
+            // was considering pulsing on attack, Tiy thinks it should be permanent while in play
+            //CurrentHedgehog^.Gear^.State:= CurrentHedgehog^.Gear^.State or gstVampiric;
+            inc(CurrentHedgehog^.Gear^.Health,vampDmg);
+            str(vampDmg, s);
+            s:= '+' + s;
+            AddCaption(s, CurrentHedgehog^.Team^.Clan^.Color, capgrpAmmoinfo);
+            RenderHealth(CurrentHedgehog^);
+            RecountTeamHealth(CurrentHedgehog^.Team);
+            end;
+        if ((GameFlags and gfKarma) <> 0) and 
+           ((GameFlags and gfInvulnerable) = 0) and
+           not CurrentHedgehog^.Gear^.Invulnerable then
+           begin // this cannot just use Damage or it interrupts shotgun and gets you called stupid
+           if CurrentHedgehog^.Gear^.Health < int(Damage) then
+           begin
+               // Add damage to trigger normal resolution
+               CurrentHedgehog^.Gear^.Health := 0;
+               inc(CurrentHedgehog^.Gear^.Damage);
+           end
+           else
+               dec(CurrentHedgehog^.Gear^.Health, Damage);
+           AddGear(hwRound(Gear^.X), 
+                   hwRound(Gear^.Y), 
+                   gtHealthTag, Damage, _0, _0, 0)^.Hedgehog:= CurrentHedgehog;
+           RenderHealth(CurrentHedgehog^);
+           RecountTeamHealth(CurrentHedgehog^.Team);
+           end;
+        end;
+    end;
+end;
 
 procedure SetAllToActive;
 var t: PGear;
@@ -841,6 +902,7 @@
 			1,
 			1,
 			0);
+	HatVisible:= true;
 	defaultPos:= false
 	end else
 
@@ -950,7 +1012,10 @@
 end else // not gstHHDriven
 	begin
 	if (Gear^.Damage > 0)
-	and (hwSqr(Gear^.dX) + hwSqr(Gear^.dY) > _0_003) then
+	and (hwSqr(Gear^.dX) + hwSqr(Gear^.dY) > _0_003) 
+// ARTILLERY
+//and (1=0)
+then
 		begin
 		DrawHedgehog(sx, sy,
 			hwSign(Gear^.dX),
@@ -1123,9 +1188,15 @@
 	end;
 
 if Gear^.Invulnerable then
-     begin
-     DrawSprite(sprInvulnerable, sx - 24, sy - 24, 0);
-     end;
+    begin
+    DrawSprite(sprInvulnerable, sx - 24, sy - 24, 0);
+    end;
+if cVampiric and
+   (CurrentHedgehog^.Gear <> nil) and 
+   (CurrentHedgehog^.Gear = Gear) then
+    begin
+    DrawSprite(sprVampiric, sx - 24, sy - 24, 0);
+    end;
 end;
 
 procedure DrawGears;
@@ -1216,6 +1287,8 @@
     gtAmmo_Grenade: DrawRotated(sprGrenade, hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy, 0, DxDy2Angle(Gear^.dY, Gear^.dX));
        
        gtHealthTag: if Gear^.Tex <> nil then DrawCentered(hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy, Gear^.Tex);
+
+       gtSpeechBubble: if Gear^.Tex <> nil then DrawCentered(hwRound(Gear^.X) + WorldDx, hwRound(Gear^.Y) + WorldDy, Gear^.Tex);
            
            gtGrave: DrawSurfSprite(hwRound(Gear^.X) + WorldDx - 16, hwRound(Gear^.Y) + WorldDy - 16, 32, (GameTicks shr 7) and 7, PHedgehog(Gear^.Hedgehog)^.Team^.GraveTex);
              
@@ -1328,6 +1401,9 @@
 if (GameFlags and gfLowGravity) <> 0 then
     cGravity:= cMaxWindSpeed / 2;
 
+if (GameFlags and gfVampiric) <> 0 then
+    cVampiric:= true;
+
 Gear:= GearsList;
 if (GameFlags and gfInvulnerable) <> 0 then
    while Gear <> nil do
@@ -1372,11 +1448,7 @@
 						if (Mask and EXPLNoDamage) = 0 then
 							begin
 							if not Gear^.Invulnerable then
-								begin
-							    inc(Gear^.Damage, dmg);
-							    if Gear^.Kind = gtHedgehog then
-								    AddDamageTag(hwRound(Gear^.X), hwRound(Gear^.Y), dmg, PHedgehog(Gear^.Hedgehog)^.Team^.Clan^.Color);
-                                end
+                                ApplyDamage(Gear, dmg)
                             else
                                 Gear^.State:= Gear^.State or gstWinner;
 							end;
@@ -1423,11 +1495,7 @@
 			gtCase,
 			gtTarget: begin
                     if (not t^.Invulnerable) then
-                        begin
-					    inc(t^.Damage, dmg);
-					    if t^.Kind = gtHedgehog then
-						    AddDamageTag(hwRound(Gear^.X), hwRound(Gear^.Y), dmg, PHedgehog(t^.Hedgehog)^.Team^.Clan^.Color);
-                        end
+                        ApplyDamage(t, dmg)
                     else
                         Gear^.State:= Gear^.State or gstWinner;
 
@@ -1468,12 +1536,7 @@
 			gtCase: begin
 					if (Ammo^.Kind = gtDrill) then begin Ammo^.Timer:= 0; exit; end;
                     if (not t^.ar[i]^.Invulnerable) then
-                        begin
-					    inc(t^.ar[i]^.Damage, Damage);
-
-                        if (t^.ar[i]^.Kind = gtHedgehog) and (Damage > 0) then
-                            AddDamageTag(hwRound(t^.ar[i]^.X), hwRound(t^.ar[i]^.Y), Damage, PHedgehog(t^.ar[i]^.Hedgehog)^.Team^.Clan^.Color);
-                        end
+                        ApplyDamage(t^.ar[i], Damage)
                     else
                         t^.ar[i]^.State:= t^.ar[i]^.State or gstWinner;
 
@@ -1587,7 +1650,7 @@
 	if (t^.Kind = gtHedgehog) and (t^.Y < Ammo^.Y) then
 		if not (hwSqr(Ammo^.X - t^.X) + hwSqr(Ammo^.Y - t^.Y - int2hwFloat(cHHRadius)) * 2 > _2) then
 			begin
-			inc(t^.Damage, 5);
+            ApplyDamage(t, 5);
 			t^.dX:= t^.dX + (t^.X - Ammo^.X) * _0_02;
 			t^.dY:= - _0_25;
 			t^.Active:= true;
@@ -1638,8 +1701,7 @@
 
 FollowGear:= nil;
 
-t:= getrandom(20);  // TEMPORARY  REMOVE WHEN CRATE PROBABILITY IS ADDED
-if shoppa then
+if shoppa then  // TEMPORARY  REMOVE WHEN CRATE PROBABILITY IS ADDED
     t:= 7
 else
     t:= getrandom(20);
--- a/hedgewars/uIO.pas	Sun Apr 26 15:47:03 2009 +0000
+++ b/hedgewars/uIO.pas	Thu Apr 30 20:13:44 2009 +0000
@@ -293,6 +293,7 @@
 		'w': ParseCommand('setweap ' + headcmd^.str[2], true);
 		't': ParseCommand('taunt ' + headcmd^.str[2], true);
 		'g': ParseCommand('newgrave', true);
+		'h': ParseCommand('hogsay ' + copy(headcmd^.str, 2, Pred(headcmd^.len)), true);
 		'1'..'5': ParseCommand('timer ' + headcmd^.cmd, true);
 		#128..char(128 + cMaxSlotIndex): ParseCommand('slot ' + char(byte(headcmd^.cmd) - 79), true)
 		else
--- a/hedgewars/uLocale.pas	Sun Apr 26 15:47:03 2009 +0000
+++ b/hedgewars/uLocale.pas	Thu Apr 30 20:13:44 2009 +0000
@@ -25,7 +25,7 @@
 			sidGirder, sidTeleport, sidSwitch, sidMortar, sidWhip,
 			sidKamikaze, sidCake, sidSeduction, sidWatermelon,
 			sidHellishBomb, sidDrill, sidBallgun, sidNapalm, sidRCPlane, 
-            sidLowGravity, sidExtraDamage, sidInvulnerable, sidExtraTime, sidLaserSight);
+            sidLowGravity, sidExtraDamage, sidInvulnerable, sidExtraTime, sidLaserSight, sidVampiric);
 
 	TMsgStrId = (sidStartFight, sidDraw, sidWinner, sidVolume, sidPaused,
 			sidConfirm, sidSuddenDeath);
--- a/hedgewars/uMisc.pas	Sun Apr 26 15:47:03 2009 +0000
+++ b/hedgewars/uMisc.pas	Thu Apr 30 20:13:44 2009 +0000
@@ -105,6 +105,7 @@
 	cGravity: hwFloat;
 	cDamageModifier: hwFloat;
 	cLaserSighting: boolean;
+	cVampiric: boolean;
 
 	flagMakeCapture: boolean = false;
 
@@ -382,7 +383,7 @@
 			for x:= Surf^.w to Pred(tw) do
 				toP4^[x]:= 0;
 			toP4:= @(toP4^[tw]);
-			fromP4:= @(fromP4^[Surf^.w]);
+			fromP4:= @(fromP4^[Surf^.pitch div 4]);
 			end;
 
 		for y:= Surf^.h to Pred(th) do
@@ -548,6 +549,7 @@
 cGravity:= cMaxWindSpeed;
 cDamageModifier:= _1;
 cLaserSighting:= false;
+cVampiric:= false;
 
 {$IFDEF DEBUGFILE}
 {$I-}
--- a/hedgewars/uStore.pas	Sun Apr 26 15:47:03 2009 +0000
+++ b/hedgewars/uStore.pas	Thu Apr 30 20:13:44 2009 +0000
@@ -18,7 +18,7 @@
 
 unit uStore;
 interface
-uses uConsts, uTeams, SDLh,
+uses sysutils, uConsts, uTeams, SDLh,
 {$IFDEF IPHONE}
 	gles11,
 {$ELSE}
@@ -45,6 +45,11 @@
 procedure DrawHedgehog(X, Y: LongInt; Dir: LongInt; Pos, Step: LongWord; Angle: real);
 procedure DrawFillRect(r: TSDL_Rect);
 function  RenderStringTex(s: string; Color: Longword; font: THWFont): PTexture;
+function  RenderSpeechBubbleTex(s: string; SpeechType: Longword; font: THWFont): PTexture;
+procedure flipSurface(Surface: PSDL_Surface; Vertical: Boolean);
+//procedure rotateSurface(Surface: PSDL_Surface);
+procedure copyRotatedSurface(src, dest: PSDL_Surface); // this is necessary since width/height are read only in SDL
+procedure copyToXY(src, dest: PSDL_Surface; destX, destY: Integer);
 procedure RenderHealth(var Hedgehog: THedgehog);
 procedure AddProgress;
 procedure FinishProgress;
@@ -672,6 +677,176 @@
 SDL_FreeSurface(Result)
 end;
 
+function RenderSpeechBubbleTex(s: string; SpeechType: Longword; font: THWFont): PTexture;
+var textWidth, textHeight, x, y, w, h, i, pos, prevpos, line, numLines, edgeWidth, edgeHeight, cornerWidth, cornerHeight: LongInt;
+    Result, tmpsurf, rotatedEdge: PSDL_Surface;
+    rect: TSDL_Rect;
+    chars: TSysCharSet = [#9,' ','.',';',':','?','!',','];
+    substr: shortstring;
+    edge, corner, tail: TSPrite;
+begin
+
+case SpeechType of
+    1: begin; 
+       edge:= sprSpeechEdge; 
+       corner:= sprSpeechCorner;
+       tail:= sprSpeechTail;
+       end;
+    2: begin; 
+       edge:= sprThoughtEdge;
+       corner:= sprThoughtCorner; 
+       tail:= sprThoughtTail;
+       end;
+    3: begin; 
+       edge:= sprShoutEdge;
+       corner:= sprShoutCorner;
+       tail:= sprShoutTail;
+       end;
+    end;
+edgeHeight:= SpritesData[edge].Height;
+edgeWidth:= SpritesData[edge].Width;
+cornerWidth:= SpritesData[corner].Width;
+cornerHeight:= SpritesData[corner].Height;
+// This one screws it up
+s:= 'This is the song that never ends.  ''cause it goes on and on my friends. Some people, started singing it not knowing what it was. And they''ll just go on singing it forever just because... This is the song that never ends...';
+// This one doesn't
+//s:= 'This is the song that never ends.  cause it goes on and on my friends. Some people, started singing it not knowing what it was. And theyll just go on singing it forever just because... This is the song that never ends... ';
+// Also screws up, but only action
+//s:= 'This is the song that never ends.  cause it goes on and on .';
+// ok in all
+// s:= 'This is the song that never ends.  cause it goes on .';
+numLines:= 1;
+
+if length(s) = 0 then s:= '...';
+
+TTF_SizeUTF8(Fontz[font].Handle, Str2PChar(s), w, h);
+textWidth:= w;
+textHeight:= h;
+if (length(s) > 20) then
+    begin
+    i:= round(Sqrt(length(s)) * 2);
+    s:= WrapText(s, #1, chars, i);
+    for pos:= 1 to length(s) do
+        if (s[pos] = #1) or (pos = length(s)) then
+            inc(numLines);
+
+    // TODO - find out why this calc doesn't do what I expect
+    if numLines = 2 then textWidth:= w div 2
+    else if numlines > 2 then textWidth:= w div (numLines-1);
+    end;
+
+textWidth:=((textWidth-(cornerWidth-edgeWidth)*2) div edgeWidth)*edgeWidth+edgeWidth;
+textHeight:=(((numlines * h)-((cornerHeight-edgeWidth)*2)) div edgeWidth)*edgeWidth+edgeWidth;
+addfilelog(inttostr(textHeight)+'=========== '+inttostr(numlines)+' x '+inttostr(h));
+//textWidth:=max(textWidth,SpritesData[tail].Width);
+rect.x:= 0;
+rect.y:= 0;
+rect.w:= textWidth + cornerWidth * 2;
+rect.h:= textHeight + cornerHeight * 2 - edgeHeight + SpritesData[tail].Height;
+//s:= inttostr(h) + ' ' + inttostr(numlines) + ' ' + inttostr(rect.x) + ' '+inttostr(rect.y) + ' ' + inttostr(rect.w) + ' ' + inttostr(rect.h) + ' ' + s;
+
+Result:= SDL_CreateRGBSurface(SDL_SWSURFACE, rect.w, rect.h, 32, RMask, GMask, BMask, AMask);
+
+TryDo(Result <> nil, 'RenderString: fail to create surface', true);
+
+//////////////////////////////// CORNERS ///////////////////////////////
+copyToXY(SpritesData[corner].Surface, Result, 0, 0); /////////////////// NW
+
+flipSurface(SpritesData[corner].Surface, true); // store all 4 versions in memory to avoid repeated flips?
+x:= 0;
+y:= textHeight + cornerHeight -1;
+copyToXY(SpritesData[corner].Surface, Result, x, y); /////////////////// SW
+
+flipSurface(SpritesData[corner].Surface, false);
+x:= rect.w-cornerWidth-1;
+y:= textHeight + cornerHeight -1;
+copyToXY(SpritesData[corner].Surface, Result, x, y); /////////////////// SE
+
+flipSurface(SpritesData[corner].Surface, true);
+x:= rect.w-cornerWidth-1;
+y:= 0;
+copyToXY(SpritesData[corner].Surface, Result, x, y); /////////////////// NE
+flipSurface(SpritesData[corner].Surface, false); // restore original position
+//////////////////////////////// END CORNERS ///////////////////////////////
+
+//////////////////////////////// EDGES //////////////////////////////////////
+x:= cornerWidth;
+y:= 0;
+while x < rect.w-cornerWidth-1 do
+    begin
+    copyToXY(SpritesData[edge].Surface, Result, x, y); ///////////////// top edge
+    inc(x,edgeWidth);
+    end;
+flipSurface(SpritesData[edge].Surface, true);
+x:= cornerWidth;
+y:= textHeight + cornerHeight*2 - edgeHeight-1;
+while x < rect.w-cornerWidth-1 do
+    begin
+    copyToXY(SpritesData[edge].Surface, Result, x, y); ///////////////// bottom edge
+    inc(x,edgeWidth);
+    end;
+flipSurface(SpritesData[edge].Surface, true); // restore original position
+
+rotatedEdge:= SDL_CreateRGBSurface(SDL_SWSURFACE, edgeHeight, edgeWidth, 32, RMask, GMask, BMask, AMask);
+x:= rect.w - edgeHeight - 1;
+y:= cornerHeight;
+//// initially was going to rotate in place, but the SDL spec claims width/height are read only
+copyRotatedSurface(SpritesData[edge].Surface,rotatedEdge);
+while y < textHeight + cornerHeight do
+    begin
+    copyToXY(rotatedEdge, Result, x, y);
+    inc(y,edgeWidth);
+    end;
+flipSurface(rotatedEdge, false); // restore original position
+x:= 0;
+y:= cornerHeight;
+while y < textHeight + cornerHeight do
+    begin
+    copyToXY(rotatedEdge, Result, x, y);
+    inc(y,edgeWidth);
+    end;
+//////////////////////////////// END EDGES //////////////////////////////////////
+
+x:= cornerWidth;
+y:= textHeight + cornerHeight * 2 - edgeHeight - 1;
+copyToXY(SpritesData[tail].Surface, Result, x, y);
+
+rect.x:= edgeHeight;
+rect.y:= edgeHeight;
+rect.w:= rect.w - edgeHeight * 2;
+rect.h:= textHeight + cornerHeight * 2 - edgeHeight * 2;
+SDL_FillRect(Result, @rect, cWhiteColor);
+
+pos:= 1; prevpos:= 0; line:= 0;
+while pos <= length(s) do
+    begin
+    if (s[pos] = #1) or (pos = length(s)) then
+        begin
+        if s[pos] <> #1 then inc(pos);
+        while s[prevpos+1] = ' 'do inc(prevpos);
+        substr:= copy(s, prevpos+1, pos-prevpos-1);
+        if Length(substr) <> 0 then
+           begin
+           tmpsurf:= TTF_RenderUTF8_Blended(Fontz[Font].Handle, Str2PChar(substr), cColorNearBlack);
+           rect.x:= edgeHeight;
+           rect.y:= edgeHeight + line * h;
+           SDLTry(tmpsurf <> nil, true);
+           SDL_UpperBlit(tmpsurf, nil, Result, @rect);
+           SDL_FreeSurface(tmpsurf);
+           inc(line);
+           prevpos:= pos;
+           end;
+        end;
+    inc(pos);
+    end;
+
+//TryDo(SDL_SetColorKey(Result, SDL_SRCCOLORKEY, 0) = 0, errmsgTransparentSet, true);
+RenderSpeechBubbleTex:= Surface2Tex(Result);
+
+SDL_FreeSurface(rotatedEdge);
+SDL_FreeSurface(Result)
+end;
+
 procedure RenderHealth(var Hedgehog: THedgehog);
 var s: shortstring;
 begin
@@ -755,4 +930,73 @@
 FreeTexture(ProgrTex)
 end;
 
+procedure flipSurface(Surface: PSDL_Surface; Vertical: Boolean);
+var y, x, i, j: LongInt;
+    tmpPixel: Longword;
+    pixels: PLongWordArray;
+begin
+TryDo(Surface^.format^.BytesPerPixel = 4, 'flipSurface failed, expecting 32 bit surface', true);
+pixels:= Surface^.pixels;
+if Vertical then
+   for y := 0 to (Surface^.h div 2) - 1 do
+       for x := 0 to Surface^.w - 1 do
+           begin
+           i:= y * Surface^.w + x;
+           j:= (Surface^.h - y - 1) * Surface^.w + x;
+           tmpPixel:= pixels^[i];
+           pixels^[i]:= pixels^[j];
+           pixels^[j]:= tmpPixel;
+           end
+else
+   for x := 0 to (Surface^.w div 2) - 1 do
+       for y := 0 to Surface^.h -1 do
+           begin
+           i:= y*Surface^.w + x;
+           j:= y*Surface^.w + (Surface^.w - x - 1);
+           tmpPixel:= pixels^[i];
+           pixels^[i]:= pixels^[j];
+           pixels^[j]:= tmpPixel;
+           end;
+end;
+
+procedure copyToXY(src, dest: PSDL_Surface; destX, destY: Integer);
+var srcX, srcY, i, j, maxDest: LongInt;
+    srcPixels, destPixels: PLongWordArray;
+begin
+addfilelog('copyToXY: src surf (w, h) = ('+inttostr(src^.w)+', '+inttostr(src^.h)+')');
+addfilelog('copyToXY: dest(X, Y) = ('+inttostr(destX)+', '+inttostr(destY)+')');
+maxDest:= (dest^.pitch div 4) * dest^.h;
+srcPixels:= src^.pixels;
+destPixels:= dest^.pixels;
+
+for srcX:= 0 to src^.w - 1 do
+   for srcY:= 0 to src^.h - 1 do
+      begin
+      i:= (destY + srcY) * (dest^.pitch div 4) + destX + srcX;
+      j:= srcY * (src^.pitch div 4) + srcX;
+      // basic skip of transparent pixels - cleverness would be to do true alpha
+      if (i < maxDest) and ($FF000000 and srcPixels^[j] <> 0) then destPixels^[i]:= srcPixels^[j];
+      end;
+end;
+
+procedure copyRotatedSurface(src, dest: PSDL_Surface); // this is necessary since width/height are read only in SDL, apparently
+var y, x, i, j: LongInt;
+    srcPixels, destPixels: PLongWordArray;
+begin
+TryDo(src^.format^.BytesPerPixel = 4, 'rotateSurface failed, expecting 32 bit surface', true);
+TryDo(dest^.format^.BytesPerPixel = 4, 'rotateSurface failed, expecting 32 bit surface', true);
+
+srcPixels:= src^.pixels;
+destPixels:= dest^.pixels;
+
+j:= 0;
+for x := 0 to src^.w - 1 do
+    for y := 0 to src^.h - 1 do
+        begin
+        i:= (src^.h - 1 - y) * (src^.pitch div 4) + x;
+        destPixels^[j]:= srcPixels^[i];
+        inc(j)
+        end;
+end;
+
 end.
--- a/hedgewars/uTeams.pas	Sun Apr 26 15:47:03 2009 +0000
+++ b/hedgewars/uTeams.pas	Thu Apr 30 20:13:44 2009 +0000
@@ -37,6 +37,7 @@
 	THedgehog = record
 			Name: string[MAXNAMELEN];
 			Gear: PGear;
+			SpeechGear: PGear;
 			NameTagTex,
 			HealthTagTex,
 			HatTex: PTexture;
--- a/share/hedgewars/Data/Locale/bg.txt	Sun Apr 26 15:47:03 2009 +0000
+++ b/share/hedgewars/Data/Locale/bg.txt	Thu Apr 30 20:13:44 2009 +0000
@@ -37,6 +37,7 @@
 00:34=Invulnerable
 00:35=Extra Time
 00:36=Laser Sight
+00:37=Vampirism
 
 01:00=Бой!
 01:01=Равен рунд
--- a/share/hedgewars/Data/Locale/cs.txt	Sun Apr 26 15:47:03 2009 +0000
+++ b/share/hedgewars/Data/Locale/cs.txt	Thu Apr 30 20:13:44 2009 +0000
@@ -37,6 +37,7 @@
 00:34=Invulnerable
 00:35=Extra Time
 00:36=Laser Sight
+00:37=Vampirism
 
 01:00=Do boje!
 01:01=Kolo nerozhodně
--- a/share/hedgewars/Data/Locale/de.txt	Sun Apr 26 15:47:03 2009 +0000
+++ b/share/hedgewars/Data/Locale/de.txt	Thu Apr 30 20:13:44 2009 +0000
@@ -37,6 +37,7 @@
 00:34=Unverwundbarkeit
 00:35=Zusatzzeit
 00:36=Laservisier
+00:37=Vampirism
 
 01:00=Auf in die Schlacht!
 01:01=Unentschieden
--- a/share/hedgewars/Data/Locale/en.txt	Sun Apr 26 15:47:03 2009 +0000
+++ b/share/hedgewars/Data/Locale/en.txt	Thu Apr 30 20:13:44 2009 +0000
@@ -37,6 +37,7 @@
 00:34=Invulnerable
 00:35=Extra Time
 00:36=Laser Sight
+00:37=Vampirism
 
 01:00=Let's fight!
 01:01=Round draw
--- a/share/hedgewars/Data/Locale/es.txt	Sun Apr 26 15:47:03 2009 +0000
+++ b/share/hedgewars/Data/Locale/es.txt	Thu Apr 30 20:13:44 2009 +0000
@@ -37,6 +37,7 @@
 00:34=Invulnerable
 00:35=Tiempo extra
 00:36=Mira láser
+00:37=Vampirism
 
 01:00=Luchad!
 01:01=Empate
--- a/share/hedgewars/Data/Locale/fi.txt	Sun Apr 26 15:47:03 2009 +0000
+++ b/share/hedgewars/Data/Locale/fi.txt	Thu Apr 30 20:13:44 2009 +0000
@@ -37,6 +37,7 @@
 00:34=Invulnerable
 00:35=Extra Time
 00:36=Laser Sight
+00:37=Vampirism
 
 01:00=Taistelu alkakoon!
 01:01=Tasapeli
--- a/share/hedgewars/Data/Locale/fr.txt	Sun Apr 26 15:47:03 2009 +0000
+++ b/share/hedgewars/Data/Locale/fr.txt	Thu Apr 30 20:13:44 2009 +0000
@@ -37,6 +37,7 @@
 00:34=Invulnérable
 00:35=Extra Time
 00:36=Laser Sight
+00:37=Vampirism
 
 01:00=C'est parti!
 01:01=Round ex aequo
--- a/share/hedgewars/Data/Locale/it.txt	Sun Apr 26 15:47:03 2009 +0000
+++ b/share/hedgewars/Data/Locale/it.txt	Thu Apr 30 20:13:44 2009 +0000
@@ -37,6 +37,7 @@
 00:34=Invulnerabilità
 00:35=Tempo Extra
 00:36=Mirino Laser
+00:37=Vampirism
 
 01:00=Combattiamo!
 01:01=Round in parità
--- a/share/hedgewars/Data/Locale/pl.txt	Sun Apr 26 15:47:03 2009 +0000
+++ b/share/hedgewars/Data/Locale/pl.txt	Thu Apr 30 20:13:44 2009 +0000
@@ -37,6 +37,7 @@
 00:34=Niezniszczalność
 00:35=Dodatkowy czas
 00:36=Celownik laserowy
+00:37=Vampirism
 
 01:00=Walczmy!
 01:01=Remis
--- a/share/hedgewars/Data/Locale/pt-br.txt	Sun Apr 26 15:47:03 2009 +0000
+++ b/share/hedgewars/Data/Locale/pt-br.txt	Thu Apr 30 20:13:44 2009 +0000
@@ -37,6 +37,7 @@
 00:34=Invulnerable
 00:35=Extra Time
 00:36=Laser Sight
+00:37=Vampirism
 
 01:00=Hora de lutar!
 01:01=Partida empatou
--- a/share/hedgewars/Data/Locale/ru.txt	Sun Apr 26 15:47:03 2009 +0000
+++ b/share/hedgewars/Data/Locale/ru.txt	Thu Apr 30 20:13:44 2009 +0000
@@ -37,6 +37,7 @@
 00:34=Неуязвимость
 00:35=30 секунд
 00:36=Лазерный прицел
+00:37=Vampirism
 
 01:00=Вперёд к победе!
 01:01=Ничья
--- a/share/hedgewars/Data/Locale/sk.txt	Sun Apr 26 15:47:03 2009 +0000
+++ b/share/hedgewars/Data/Locale/sk.txt	Thu Apr 30 20:13:44 2009 +0000
@@ -37,6 +37,7 @@
 00:34=Nesmrteľnosť
 00:35=Extra čas
 00:36=Laserové zameriavanie
+00:37=Vampirism
 
 01:00=Do boja!
 01:01=Remíza
--- a/share/hedgewars/Data/Locale/tr.txt	Sun Apr 26 15:47:03 2009 +0000
+++ b/share/hedgewars/Data/Locale/tr.txt	Thu Apr 30 20:13:44 2009 +0000
@@ -37,6 +37,7 @@
 00:34=Ölümsüzlük
 00:35=Arttırılmış Zaman
 00:36=Lazer Görüşü
+00:37=Vampirism
 
 01:00=Savaş başlasın!
 01:01=Beraberlik
--- a/share/hedgewars/Data/Locale/uk.txt	Sun Apr 26 15:47:03 2009 +0000
+++ b/share/hedgewars/Data/Locale/uk.txt	Thu Apr 30 20:13:44 2009 +0000
@@ -37,6 +37,7 @@
 00:34=Invulnerable
 00:35=Extra Time
 00:36=Laser Sight
+00:37=Vampirism
 
 01:00=Уперед до перемоги!
 01:01=Нічия
--- a/share/hedgewars/Data/Locale/zh_CN.txt	Sun Apr 26 15:47:03 2009 +0000
+++ b/share/hedgewars/Data/Locale/zh_CN.txt	Thu Apr 30 20:13:44 2009 +0000
@@ -37,6 +37,7 @@
 00:34=刀枪不入
 00:35=加时
 00:36=激光瞄准
+00:37=Vampirism
 
 01:00=战斗啦!
 01:01=平手
--- a/share/hedgewars/Data/Locale/zh_TW.txt	Sun Apr 26 15:47:03 2009 +0000
+++ b/share/hedgewars/Data/Locale/zh_TW.txt	Thu Apr 30 20:13:44 2009 +0000
@@ -35,6 +35,7 @@
 00:34=刀槍不入
 00:35=附加時間
 00:36=雷射瞄准
+00:37=Vampirism
 
 01:00=戰鬥開始!
 01:01=平手