# HG changeset patch # User unC0Rr # Date 1692686146 -7200 # Node ID 2146cb7be36f3a783a732ebfefda2dd7252bc49d # Parent 772a43d88e6b2d147e42240f6df355e2b62cd2de# Parent 8bb07b0f50ca3d1aca09682240c2c26c44d892a5 Merge default diff -r 772a43d88e6b -r 2146cb7be36f CMakeLists.txt --- a/CMakeLists.txt Fri Mar 10 11:42:25 2023 +0100 +++ b/CMakeLists.txt Tue Aug 22 08:35:46 2023 +0200 @@ -1,8 +1,8 @@ +cmake_minimum_required(VERSION 2.6.4) + project(hedgewars) - #initialise cmake environment -cmake_minimum_required(VERSION 2.6.4) foreach(hwpolicy CMP0003 CMP0012 CMP0017 CMP0018) if(POLICY ${hwpolicy}) cmake_policy(SET ${hwpolicy} NEW) @@ -177,6 +177,7 @@ "-fno-warn-unused-do-bind" "-O0" ) + set(USE_DEBUG_LIBRARIES TRUE) else() list(APPEND haskell_flags "-w" # no warnings "-O2" @@ -249,7 +250,15 @@ if(PHYSFS_LIBRARY AND PHYSFS_INCLUDE_DIR) #use an IMPORTED tharget so that we can just use 'physfs' to link add_library(physfs UNKNOWN IMPORTED) - set_target_properties(physfs PROPERTIES IMPORTED_LOCATION ${PHYSFS_LIBRARY}) + if (DEFINED PHYSFS_LIBRARY_RELEASE) + if (${USE_DEBUG_LIBRARIES}) + set_target_properties(physfs PROPERTIES IMPORTED_LOCATION ${PHYSFS_LIBRARY_DEBUG}) + else() + set_target_properties(physfs PROPERTIES IMPORTED_LOCATION ${PHYSFS_LIBRARY_RELEASE}) + endif() + else() + set_target_properties(physfs PROPERTIES IMPORTED_LOCATION ${PHYSFS_LIBRARY}) + endif() else() message(FATAL_ERROR "Missing PhysFS! Install PhysFS to fix this.") endif() diff -r 772a43d88e6b -r 2146cb7be36f QTfrontend/CMakeLists.txt --- a/QTfrontend/CMakeLists.txt Fri Mar 10 11:42:25 2023 +0100 +++ b/QTfrontend/CMakeLists.txt Tue Aug 22 08:35:46 2023 +0200 @@ -14,7 +14,7 @@ include(CheckLibraryExists) find_package(SDL2 REQUIRED CONFIG) -find_package(SDL2_mixer 2 REQUIRED) #audio in SDLInteraction +find_package(SDL2_mixer REQUIRED CONFIG) #audio in SDLInteraction include_directories(${SDL2_INCLUDE_DIRS}) include_directories(${SDL2_MIXER_INCLUDE_DIRS}) @@ -229,16 +229,13 @@ Qt5::Core Qt5::Widgets Qt5::Gui Qt5::Network ) -list(APPEND HW_LINK_LIBS - ${SDL2_LIBRARIES} - ${SDL2_MIXER_LIBRARIES} - ) +if(WIN32 AND VCPKG_TOOLCHAIN) + list(APPEND HW_LINK_LIBS SDL2::SDL2 SDL2_mixer::SDL2_mixer) +else() + list(APPEND HW_LINK_LIBS ${SDL2_LIBRARIES} ${SDL2_MIXER_LIBRARY}) +endif() if(WIN32 AND NOT UNIX) - if(NOT SDL2_LIBRARIES) - list(APPEND HW_LINK_LIBS SDL2::SDL2) - endif() - list(APPEND HW_LINK_LIBS ole32 oleaut32 diff -r 772a43d88e6b -r 2146cb7be36f QTfrontend/hedgewars.qrc --- a/QTfrontend/hedgewars.qrc Fri Mar 10 11:42:25 2023 +0100 +++ b/QTfrontend/hedgewars.qrc Tue Aug 22 08:35:46 2023 +0200 @@ -1,6 +1,7 @@ ../share/hedgewars/Data/Graphics/AmmoMenu/Ammos_base.png + ../share/hedgewars/Data/Graphics/AmmoMenu/Ammos_ExtraDamage_comma.png ../share/hedgewars/Data/misc/keys.csv res/css/qt.css res/css/chat.css diff -r 772a43d88e6b -r 2146cb7be36f QTfrontend/hwconsts.h --- a/QTfrontend/hwconsts.h Fri Mar 10 11:42:25 2023 +0100 +++ b/QTfrontend/hwconsts.h Tue Aug 22 08:35:46 2023 +0200 @@ -136,3 +136,5 @@ 7, 21, 32, 33, 35, 60\ } +/* Ammo ID for extra damage */ +#define HW_AMMOTYPE_EXTRADAMAGE 32 diff -r 772a43d88e6b -r 2146cb7be36f QTfrontend/res/credits.csv --- a/QTfrontend/res/credits.csv Fri Mar 10 11:42:25 2023 +0100 +++ b/QTfrontend/res/credits.csv Tue Aug 22 08:35:46 2023 +0200 @@ -172,6 +172,7 @@ E,"Scottish Gaelic",,,"GunChleoc" E,"Slovak","Jose Riha",, E,"Spanish","Carlos Vives","mail@carlosvives.es", +E,"Spanish",,,"salvadorc17" E,"Swedish","Niklas Grahn","raewolusjoon@yaoo.com", E,"Swedish","Henrik Rostedt","henrik.rostedt@gmail.com", E,"Ukrainian","Eugene V. Lyubimkin","jackyf.devel@gmail.com", diff -r 772a43d88e6b -r 2146cb7be36f QTfrontend/ui/widget/selectWeapon.cpp --- a/QTfrontend/ui/widget/selectWeapon.cpp Fri Mar 10 11:42:25 2023 +0100 +++ b/QTfrontend/ui/widget/selectWeapon.cpp Tue Aug 22 08:35:46 2023 +0200 @@ -38,11 +38,20 @@ QImage getAmmoImage(int num) { - static QImage ammo(":Ammos.png"); - int x = num/(ammo.height()/32); - int y = (num-((ammo.height()/32)*x))*32; - x*=32; - return ammo.copy(x, y, 32, 32); + // Show ammo image for ammo selection menu + if (QLocale().decimalPoint() == "," && num == HW_AMMOTYPE_EXTRADAMAGE) { + // Special case: Extra Damage icon showing "1,5" instead of "1.5" if locale + // uses comma as decimal separator + static QImage extradamage(":Ammos_ExtraDamage_comma.png"); + return extradamage; + } else { + // Normal case: Pick icon from Ammos.png + static QImage ammo(":Ammos.png"); + int x = num/(ammo.height()/32); + int y = (num-((ammo.height()/32)*x))*32; + x*=32; + return ammo.copy(x, y, 32, 32); + } } SelWeaponItem::SelWeaponItem(bool allowInfinite, int iconNum, int wNum, QImage image, QImage imagegrey, QWidget* parent) : diff -r 772a43d88e6b -r 2146cb7be36f README.md --- a/README.md Fri Mar 10 11:42:25 2023 +0100 +++ b/README.md Tue Aug 22 08:35:46 2023 +0200 @@ -144,6 +144,6 @@ Contact ------- * Homepage - https://hedgewars.org/ -* IRC channel - irc://irc.freenode.net/hedgewars +* IRC channel - irc://irc.libera.chat/hedgewars * Community forum - https://hedgewars.org/forum diff -r 772a43d88e6b -r 2146cb7be36f hedgewars/CMakeLists.txt --- a/hedgewars/CMakeLists.txt Fri Mar 10 11:42:25 2023 +0100 +++ b/hedgewars/CMakeLists.txt Tue Aug 22 08:35:46 2023 +0200 @@ -1,10 +1,10 @@ enable_language(Pascal) find_package(SDL2 REQUIRED CONFIG) -find_package(SDL2_image 2 REQUIRED) -find_package(SDL2_net 2 REQUIRED) -find_package(SDL2_ttf 2 REQUIRED) -find_package(SDL2_mixer 2 REQUIRED) +find_package(SDL2_image REQUIRED CONFIG) +find_package(SDL2_net REQUIRED CONFIG) +find_package(SDL2_ttf REQUIRED CONFIG) +find_package(SDL2_mixer REQUIRED CONFIG) include(CheckLibraryExists) include(${CMAKE_MODULE_PATH}/utils.cmake) @@ -22,6 +22,12 @@ endif() endif(UNIX) +# FPC 3.2.2 does not create s COFF file for the engine icon, but still includes it +# in the list of files to be linked, leading to a linking failure +if(${CMAKE_Pascal_COMPILER_VERSION} VERSION_GREATER_EQUAL 3.2) + add_flag_append(CMAKE_Pascal_FLAGS "-dSKIP_RESOURCES") +endif() + # convert list into pascal array if(FONTS_DIRS) list(LENGTH FONTS_DIRS ndirs) @@ -168,8 +174,18 @@ endif() # PhysFS -get_filename_component(PHYSFS_LIBRARY_DIR ${PHYSFS_LIBRARY} PATH) -add_flag_append(CMAKE_Pascal_FLAGS "-Fl${PHYSFS_LIBRARY}") +if (DEFINED PHYSFS_LIBRARY_RELEASE) + if(${USE_DEBUG_LIBRARIES}) + get_filename_component(PHYSFS_LIBRARY_DIR ${PHYSFS_LIBRARY_DEBUG} PATH) + add_flag_append(CMAKE_Pascal_FLAGS "-Fl${PHYSFS_LIBRARY_DEBUG}") + else() + get_filename_component(PHYSFS_LIBRARY_DIR ${PHYSFS_LIBRARY_RELEASE} PATH) + add_flag_append(CMAKE_Pascal_FLAGS "-Fl${PHYSFS_LIBRARY_RELEASE}") + endif() +else() + get_filename_component(PHYSFS_LIBRARY_DIR ${PHYSFS_LIBRARY} PATH) + add_flag_append(CMAKE_Pascal_FLAGS "-Fl${PHYSFS_LIBRARY}") +endif() list(APPEND HW_LINK_LIBS physlayer) diff -r 772a43d88e6b -r 2146cb7be36f hedgewars/hwengine.pas --- a/hedgewars/hwengine.pas Fri Mar 10 11:42:25 2023 +0100 +++ b/hedgewars/hwengine.pas Tue Aug 22 08:35:46 2023 +0200 @@ -19,8 +19,10 @@ {$INCLUDE "options.inc"} {$IFDEF WINDOWS} +{$IFNDEF SKIP_RESOURCES} {$R res/hwengine.rc} {$ENDIF} +{$ENDIF} {$IFDEF HWLIBRARY} unit hwengine; diff -r 772a43d88e6b -r 2146cb7be36f hedgewars/uAIMisc.pas --- a/hedgewars/uAIMisc.pas Fri Mar 10 11:42:25 2023 +0100 +++ b/hedgewars/uAIMisc.pas Tue Aug 22 08:35:46 2023 +0200 @@ -148,7 +148,7 @@ (Gear^.Health = 0) and (Gear^.Damage < 35)) ) and - (Targets.Count < 256) then + (Targets.Count < 255) then begin with Targets.ar[Targets.Count] do begin diff -r 772a43d88e6b -r 2146cb7be36f hedgewars/uChat.pas --- a/hedgewars/uChat.pas Fri Mar 10 11:42:25 2023 +0100 +++ b/hedgewars/uChat.pas Tue Aug 22 08:35:46 2023 +0200 @@ -27,6 +27,8 @@ procedure freeModule; procedure ReloadLines; procedure CleanupInput; +procedure CloseChat; +procedure RestoreChat; procedure AddChatString(s: shortstring); procedure DrawChat; procedure KeyPressChat(keysym: TSDL_Keysym); @@ -54,6 +56,7 @@ var Strs: array[0 .. MaxStrIndex] of TChatLine; MStrs: array[0 .. MaxStrIndex] of shortstring; LocalStrs: array[0 .. MaxStrIndex] of shortstring; + oldInput: shortstring; missedCount: LongWord; lastStr: LongWord; localLastStr: LongInt; @@ -782,6 +785,45 @@ ResetKbd; end; +procedure OpenChat(s: shortstring); +var i: Integer; +begin + if GameState = gsConfirm then + ParseCommand('quit', true); + isInChatMode:= true; + SDL_StopTextInput(); + SDL_StartTextInput(); + //Make REALLY sure unexpected events are flushed (1 time is insufficient as of SDL 2.0.7) + for i := 1 to 2 do + begin + SDL_PumpEvents(); + SDL_FlushEvent(SDL_TEXTINPUT); + end; + if length(s) = 0 then + SetLine(InputStr, '', true) + else + begin + SetLine(InputStr, s, true); + cursorPos:= length(s); + UpdateCursorCoords(); + end; +end; + +procedure CloseChat; +begin + oldInput:= InputStr.s; + SetLine(InputStr, '', true); + ResetCursor(); + CleanupInput(); +end; + +procedure RestoreChat; +begin + if length(oldInput) > 0 then + OpenChat(oldInput); + oldInput:= ''; +end; + procedure DelBytesFromInputStrBack(endIdx: integer; count: byte); var startIdx: integer; begin @@ -1062,7 +1104,9 @@ SetLine(InputStr, '', true); ResetCursor(); end - else CleanupInput + else + CleanupInput; + oldInput:= ''; end; SDL_SCANCODE_RETURN, SDL_SCANCODE_KP_ENTER: begin @@ -1337,27 +1381,12 @@ end; procedure chChat(var s: shortstring); -var i: Integer; begin s:= s; // avoid compiler hint - isInChatMode:= true; - SDL_StopTextInput(); - SDL_StartTextInput(); - //Make REALLY sure unexpected events are flushed (1 time is insufficient as of SDL 2.0.7) - for i := 1 to 2 do - begin - SDL_PumpEvents(); - SDL_FlushEvent(SDL_TEXTINPUT); - end; - //SDL_EnableKeyRepeat(200,45); if length(s) = 0 then - SetLine(InputStr, '', true) + OpenChat('') else - begin - SetLine(InputStr, '/clan ', true); - cursorPos:= 6; - UpdateCursorCoords(); - end; + OpenChat('/clan '); end; procedure initModule; diff -r 772a43d88e6b -r 2146cb7be36f hedgewars/uCommandHandlers.pas --- a/hedgewars/uCommandHandlers.pas Fri Mar 10 11:42:25 2023 +0100 +++ b/hedgewars/uCommandHandlers.pas Tue Aug 22 08:35:46 2023 +0200 @@ -27,7 +27,7 @@ implementation uses uCommands, uTypes, uVariables, uIO, uDebug, uConsts, uScript, uUtils, SDLh, uWorld, uRandom, uCaptions - , uVisualGearsList, uGearsHedgehog + , uVisualGearsList, uGearsHedgehog, uChat {$IFDEF USE_VIDEO_RECORDING}, uVideoRec {$ENDIF}; var cTagsMasks : array[0..15] of byte = (7, 0, 0, 0, 0, 4, 5, 6, 15, 8, 8, 8, 8, 12, 13, 14); @@ -49,15 +49,16 @@ begin s:= s; // avoid compiler hint if (GameState = gsGame) then - begin - isInChatMode:= false; + begin + CloseChat; GameState:= gsConfirm; - end - else begin + end + else if GameState = gsConfirm then + begin GameState:= gsGame; - end; - + RestoreChat; + end; updateCursorVisibility; end; diff -r 772a43d88e6b -r 2146cb7be36f hedgewars/uConsts.pas --- a/hedgewars/uConsts.pas Fri Mar 10 11:42:25 2023 +0100 +++ b/hedgewars/uConsts.pas Tue Aug 22 08:35:46 2023 +0200 @@ -171,7 +171,7 @@ cVisibleWater : LongInt = 128; cTeamHealthWidth : LongInt = 128; - cTeamHealthHeight : LongInt = 19 * HDPIScaleFactor; + cTeamHealthHeight : LongInt = round(19 * HDPIScaleFactor); cGearContourThreshold : LongInt = 179; // if water opacity is higher than this, draw contour for some gears when in water cifRandomize = $00000001; @@ -221,8 +221,8 @@ cKbdMaxIndex = 65536;//need more room for the modifier keys // font stuff - cFontBorder = 2 * HDPIScaleFactor; - cFontPadding = 2 * HDPIScaleFactor; + cFontBorder = round(2 * HDPIScaleFactor); + cFontPadding = round(2 * HDPIScaleFactor); cDefaultBuildMaxDist = 256; // default max. building distance with girder/rubber cResurrectorDist = 100; // effect distance of resurrector diff -r 772a43d88e6b -r 2146cb7be36f hedgewars/uGears.pas --- a/hedgewars/uGears.pas Fri Mar 10 11:42:25 2023 +0100 +++ b/hedgewars/uGears.pas Tue Aug 22 08:35:46 2023 +0200 @@ -288,6 +288,7 @@ curHandledGear^.Tex:= RenderStringTex(trmsg[sidUnknownGearValue], $ff808080, fntSmall) else begin + FreeAndNilTexture(curHandledGear^.Tex); // Display mine timer with up to 1 decimal point of precision (rounded down) i:= curHandledGear^.Timer div 1000; j:= (curHandledGear^.Timer mod 1000) div 100; diff -r 772a43d88e6b -r 2146cb7be36f hedgewars/uGearsHandlersMess.pas --- a/hedgewars/uGearsHandlersMess.pas Fri Mar 10 11:42:25 2023 +0100 +++ b/hedgewars/uGearsHandlersMess.pas Tue Aug 22 08:35:46 2023 +0200 @@ -264,6 +264,7 @@ procedure HideHog(HH: PHedgehog); begin + if HH^.Gear = nil then exit; ScriptCall('onHogHide', HH^.Gear^.Uid); DeleteCI(HH^.Gear); if FollowGear = HH^.Gear then @@ -6409,6 +6410,7 @@ procedure doStepTardisWarp(Gear: PGear); var HH: PHedgehog; i,j,cnt: LongWord; + restoreBottomY: LongInt; s: ansistring; begin HH:= Gear^.Hedgehog; @@ -6505,8 +6507,37 @@ inc(cnt); if (cnt = 0) or SuddenDeathDmg or (Gear^.Timer = 0) then begin + // Place tardis if HH^.GearHidden <> nil then - FindPlace(HH^.GearHidden, false, 0, LAND_WIDTH, true, false); + begin + restoreBottomY:= cWaterLine; + // Place tardis at a random safe position + FindPlace(HH^.GearHidden, false, 0, LAND_WIDTH, restoreBottomY, true, false); + + // If in Sudden Death, rise the minimum possible spawn position to make + // it less likely for the hog to drown before its turn + if SuddenDeathActive and (cWaterRise > 0) then + begin + // Enough space to survive the water rise of 1 round. + // Also limit the highest spawn height to topY plus a small buffer zone + restoreBottomY:= max(topY + cHHRadius * 5, cWaterLine - cWaterRise * (TeamsCount + 1)); + // If gear is below the safe spawn height, place it again, + // but this time with the height limit in place + if (HH^.GearHidden <> nil) and (hwRound(HH^.GearHidden^.Y) > restoreBottomY) then + // Due to the reduced Y range, this one might fail for very aggressive SD water rise + begin + FindPlace(HH^.GearHidden, false, 0, LAND_WIDTH, restoreBottomY, true, false); + end; + // Still unsafe? Relax the height limit to a third of the map height above cWaterLine + if (HH^.GearHidden <> nil) and (hwRound(HH^.GearHidden^.Y) > restoreBottomY) then + begin + restoreBottomY:= cWaterLine - ((cWaterLine - topY) div 3); + // Even this might fail, but it's much less likely. If it fails, we still have the + // position of the first FindPlace as a fallback. + FindPlace(HH^.GearHidden, false, 0, LAND_WIDTH, restoreBottomY, true, false); + end; + end; + end; if HH^.GearHidden <> nil then begin diff -r 772a43d88e6b -r 2146cb7be36f hedgewars/uGearsUtils.pas --- a/hedgewars/uGearsUtils.pas Fri Mar 10 11:42:25 2023 +0100 +++ b/hedgewars/uGearsUtils.pas Tue Aug 22 08:35:46 2023 +0200 @@ -42,6 +42,7 @@ procedure FindPlace(var Gear: PGear; withFall: boolean; Left, Right: LongInt); procedure FindPlace(var Gear: PGear; withFall: boolean; Left, Right: LongInt; skipProximity: boolean); procedure FindPlace(var Gear: PGear; withFall: boolean; Left, Right: LongInt; skipProximity, deleteOnFail: boolean); +procedure FindPlace(var Gear: PGear; withFall: boolean; Left, Right, Bottom: LongInt; skipProximity, deleteOnFail: boolean); function CountLand(x, y, r, c: LongInt; mask, antimask: LongWord): LongInt; function CheckGearNear(Kind: TGearType; X, Y: hwFloat; rX, rY: LongInt): PGear; @@ -934,7 +935,12 @@ FindPlace(Gear, withFall, Left, Right, skipProximity, true); end; -procedure FindPlace(var Gear: PGear; withFall: boolean; Left, Right: LongInt; skipProximity, deleteOnFail: boolean); +procedure FindPlace(var Gear: PGear; withFall: boolean; Left, Right: LongInt; skipProximity, deleteOnFail: boolean); inline; +begin + FindPlace(Gear, withFall, Left, Right, cWaterLine, skipProximity, deleteOnFail); +end; + +procedure FindPlace(var Gear: PGear; withFall: boolean; Left, Right, Bottom: LongInt; skipProximity, deleteOnFail: boolean); var x: LongInt; y, sy, dir: LongInt; ar: array[0..1023] of TPoint; @@ -963,11 +969,11 @@ repeat cnt:= 0; y:= min(1024, topY) - Gear^.Radius shl 1; - while y < cWaterLine do + while y < Bottom do begin repeat inc(y, 2); - until (y >= cWaterLine) or + until (y >= Bottom) or (ignoreOverLap and (CountLand(x, y, Gear^.Radius - 1, 1, lfLandMask, 0) = 0)) or (not ignoreOverLap and (CountLand(x, y, Gear^.Radius - 1, 1, lfAll, 0) = 0)); @@ -975,13 +981,13 @@ repeat inc(y); - until (y >= cWaterLine) or - (ignoreOverlap and + until (y >= Bottom) or + (ignoreOverlap and (CountLand(x, y, Gear^.Radius - 1, 1, lfAll, 0) <> 0)) or (not ignoreOverlap and (CountLand(x, y, Gear^.Radius - 1, 1, lfLandMask, 0) <> 0)); - if (y - sy > Gear^.Radius * 2) and (y < cWaterLine) + if (y - sy > Gear^.Radius * 2) and (y < Bottom) and (((Gear^.Kind = gtExplosives) and (ignoreNearObjects or NoGearsToAvoid(x, y - Gear^.Radius, 60, 60)) and (isSteadyPosition(x, y+1, Gear^.Radius - 1, 3, lfAll) diff -r 772a43d88e6b -r 2146cb7be36f hedgewars/uLocale.pas --- a/hedgewars/uLocale.pas Fri Mar 10 11:42:25 2023 +0100 +++ b/hedgewars/uLocale.pas Tue Aug 22 08:35:46 2023 +0200 @@ -164,6 +164,10 @@ 8: curArg:= arg9; end; + // Replace % sign in argument with ASCII ESC + // to prevent infinite loop below. + ReplaceChars(curArg, '%', Char($1B)); + repeat p:= Pos('%'+IntToStr(i+1), tempstr); if (p <> 0) then @@ -173,6 +177,8 @@ end; until (p = 0); end; + +ReplaceChars(tempstr, Char($1B), '%'); Format:= tempstr; end; @@ -196,6 +202,10 @@ 8: curArg:= arg9; end; + // Replace % sign in argument with ASCII ESC + // to prevent infinite loop below. + ReplaceCharsA(curArg, '%', Char($1B)); + repeat p:= Pos('%'+IntToStr(i+1), tempstr); if (p <> 0) then @@ -205,6 +215,8 @@ end; until (p = 0); end; + +ReplaceCharsA(tempstr, Char($1B), '%'); FormatA:= tempstr; end; diff -r 772a43d88e6b -r 2146cb7be36f hedgewars/uRenderUtils.pas --- a/hedgewars/uRenderUtils.pas Fri Mar 10 11:42:25 2023 +0100 +++ b/hedgewars/uRenderUtils.pas Tue Aug 22 08:35:46 2023 +0200 @@ -29,7 +29,10 @@ procedure copyToXY(src, dest: PSDL_Surface; destX, destY: LongInt); procedure copyToXYFromRect(src, dest: PSDL_Surface; srcX, srcY, srcW, srcH, destX, destY: LongInt); -procedure DrawSprite2Surf(sprite: TSprite; dest: PSDL_Surface; x,y: LongInt); +function GetSurfaceFrameCoordinateX(Surface: PSDL_Surface; Frame, frameWidth, frameHeight: LongInt): LongInt; +function GetSurfaceFrameCoordinateY(Surface: PSDL_Surface; Frame, frameHeight: LongInt): LongInt; + +procedure DrawSprite2Surf(sprite: TSprite; dest: PSDL_Surface; x,y: LongInt); procedure DrawSpriteFrame2Surf(sprite: TSprite; dest: PSDL_Surface; x,y: LongInt; frame: LongInt); procedure DrawLine2Surf(dest: PSDL_Surface; x0,y0,x1,y1:LongInt; r,g,b: byte); procedure DrawRoundRect(rect: PSDL_Rect; BorderColor, FillColor: Longword; Surface: PSDL_Surface; Clear: boolean); @@ -78,7 +81,25 @@ WriteInRoundRect:= WriteInRoundRect(Surface, X, Y, Color, Font, s, 0); end;*) -function IsTooDarkToRead(TextColor: LongWord): boolean; +function GetSurfaceFrameCoordinateX(Surface: PSDL_Surface; Frame, frameWidth, frameHeight: LongInt): LongInt; +var nx, ny: LongInt; +begin + nx:= Surface^.w div frameWidth; // number of horizontal frames + if nx = 0 then nx:= 1; // one frame is minimum + ny:= Surface^.h div frameHeight; // number of vertical frames + if ny = 0 then ny:= 1; + GetSurfaceFrameCoordinateX:= (Frame div ny) * frameWidth; +end; + +function GetSurfaceFrameCoordinateY(Surface: PSDL_Surface; Frame, frameHeight: LongInt): LongInt; +var ny: LongInt; +begin + ny:= Surface^.h div frameHeight; // number of vertical frames + if ny = 0 then ny:= 1; // one frame is minimum + GetSurfaceFrameCoordinateY:= (Frame mod ny) * frameHeight; +end; + +function IsTooDarkToRead(TextColor: LongWord): boolean; var clr: TSDL_Color; begin clr.r:= (TextColor shr 16) and $FF; @@ -94,7 +115,7 @@ clr: TSDL_Color; begin TTF_SizeUTF8(Fontz[Font].Handle, PChar(s), @w, @h); - if (maxLength > 0) and (w > maxLength * HDPIScaleFactor) then w := maxLength * HDPIScaleFactor; + if (maxLength > 0) and (w > round(maxLength * HDPIScaleFactor)) then w := round(maxLength * HDPIScaleFactor); finalRect.x:= X; finalRect.y:= Y; finalRect.w:= w + cFontBorder * 2 + cFontPadding * 2; @@ -322,6 +343,29 @@ end; +{$IFNDEF PAS2C} +// Wraps the text s by inserting breakStr as newlines with +// maximum column length maxCol. +// Same as Pascal's WrapText, but without the annoying +// behavior that text enclosed in " and ' disables word-wrapping +function SimpleWrapText(s, breakStr: string; maxCol: integer): string; +var + breakChars: set of char = [#9,' ','-']; +begin + // escape the " and ' characters before calling WrapText + // using ASCII ESC control character + s:= StringReplace(s, '"', #27+'Q', [rfReplaceAll]); + s:= StringReplace(s, '''', #27+'q', [rfReplaceAll]); + + s:= WrapText(s, #1, breakChars, maxCol); + + // Undo the escapes + s:= StringReplace(s, #27+'Q', '"', [rfReplaceAll]); + s:= StringReplace(s, #27+'q', '''', [rfReplaceAll]); + SimpleWrapText:= s; +end; +{$ENDIF} + function RenderStringTex(s: ansistring; Color: Longword; font: THWFont): PTexture; begin RenderStringTex:= RenderStringTexLim(s, Color, font, 0); @@ -341,7 +385,7 @@ font:= CheckCJKFont(s, font); w:= 0; h:= 0; // avoid compiler hints TTF_SizeUTF8(Fontz[font].Handle, PChar(s), @w, @h); - if (maxLength > 0) and (w > maxLength * HDPIScaleFactor) then w := maxLength * HDPIScaleFactor; + if (maxLength > 0) and (w > round(maxLength * HDPIScaleFactor)) then w := round(maxLength * HDPIScaleFactor); finalSurface:= SDL_CreateRGBSurface(SDL_SWSURFACE, w + cFontBorder*2 + cFontPadding*2, h + cFontBorder * 2, 32, RMask, GMask, BMask, AMask); @@ -413,9 +457,6 @@ var textWidth, textHeight, x, y, w, h, i, j, pos, line, numLines, edgeWidth, edgeHeight, cornerWidth, cornerHeight: LongInt; finalSurface, tmpsurf, rotatedEdge: PSDL_Surface; rect: TSDL_Rect; - {$IFNDEF PAS2C} - breakChars: set of char = [#9,' ','-']; - {$ENDIF} substr: ansistring; edge, corner, tail: TSPrite; begin @@ -444,10 +485,6 @@ edgeWidth:= SpritesData[edge].Width; cornerWidth:= SpritesData[corner].Width; cornerHeight:= SpritesData[corner].Height; - // This one screws up WrapText - //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 does not - //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 will go on singing it forever just because... This is the song that never ends... '; numLines:= 0; @@ -464,7 +501,7 @@ w:= 0; i:= round(Sqrt(length(s)) * 2); {$IFNDEF PAS2C} - s:= WrapText(s, #1, breakChars, i); + s:= SimpleWrapText(s, #1, i); {$ENDIF} pos:= 1; line:= 0; // Find the longest line for the purposes of centring the text. Font dependant. diff -r 772a43d88e6b -r 2146cb7be36f hedgewars/uScript.pas --- a/hedgewars/uScript.pas Fri Mar 10 11:42:25 2023 +0100 +++ b/hedgewars/uScript.pas Tue Aug 22 08:35:46 2023 +0200 @@ -308,7 +308,33 @@ LuaToSoundOrd:= i; end; -function LuaToHogEffectOrd(L : Plua_State; i: LongInt; call, paramsyntax: shortstring): LongInt; +function LuaToMsgStrIdOrd(L : Plua_State; i: LongInt; call, paramsyntax: shortstring): LongInt; inline; +begin + if lua_isnoneornil(L, i) then i:= -1 + else i:= Trunc(lua_tonumber(L, i)); + if (i < ord(Low(TMsgStrId))) or (i > ord(High(TMsgStrId))) then + begin + LuaCallError('Invalid message ID!', call, paramsyntax); + LuaToMsgStrIdOrd:= -1; + end + else + LuaToMsgStrIdOrd:= i; +end; + +function LuaToGoalStrIdOrd(L : Plua_State; i: LongInt; call, paramsyntax: shortstring): LongInt; inline; +begin + if lua_isnoneornil(L, i) then i:= -1 + else i:= Trunc(lua_tonumber(L, i)); + if (i < ord(Low(TGoalStrId))) or (i > ord(High(TGoalStrId))) then + begin + LuaCallError('Invalid goal string ID!', call, paramsyntax); + LuaToGoalStrIdOrd:= -1; + end + else + LuaToGoalStrIdOrd:= i; +end; + +function LuaToHogEffectOrd(L : Plua_State; i: LongInt; call, paramsyntax: shortstring): LongInt; begin if lua_isnoneornil(L, i) then i:= -1 else i:= Trunc(lua_tonumber(L, i)); @@ -456,13 +482,13 @@ function lc_setweapon(L : Plua_State) : LongInt; Cdecl; var at: LongInt; const - call = 'SetWeapon'; - params = 'ammoType'; + callStr = 'SetWeapon'; + paramsStr = 'ammoType'; begin // no point to run this without any CurrentHedgehog - if (CurrentHedgehog <> nil) and (CheckLuaParamCount(L, 1, call, params)) then + if (CurrentHedgehog <> nil) and (CheckLuaParamCount(L, 1, callStr, paramsStr)) then begin - at:= LuaToAmmoTypeOrd(L, 1, call, params); + at:= LuaToAmmoTypeOrd(L, 1, callStr, paramsStr); if at >= 0 then ParseCommand('setweap ' + char(at), true, true); end; @@ -472,10 +498,10 @@ // enable/disable cinematic effects function lc_setcinematicmode(L : Plua_State) : LongInt; Cdecl; const - call = 'SetCinematicMode'; - params = 'enable'; -begin - if (CheckLuaParamCount(L, 1, call, params)) then + callStr = 'SetCinematicMode'; + paramsStr = 'enable'; +begin + if (CheckLuaParamCount(L, 1, callStr, paramsStr)) then begin CinematicScript:= lua_toboolean(L, 1); end; @@ -486,10 +512,10 @@ function lc_setmaxbuilddistance(L : Plua_State) : LongInt; Cdecl; var np: LongInt; const - call = 'SetMaxBuildDistance'; - params = '[ distInPx ]'; -begin - if CheckAndFetchParamCountRange(L, 0, 1, call, params, np) then + callStr = 'SetMaxBuildDistance'; + paramsStr = '[ distInPx ]'; +begin + if CheckAndFetchParamCountRange(L, 0, 1, callStr, paramsStr, np) then begin if np = 0 then begin @@ -508,10 +534,10 @@ nextAmmo : TAmmo; s, a, cs, fa: LongInt; const - call = 'SetNextWeapon'; - params = ''; -begin - if (CurrentHedgehog <> nil) and (CheckLuaParamCount(L, 0, call, params)) then + callStr = 'SetNextWeapon'; + paramsStr = ''; +begin + if (CurrentHedgehog <> nil) and (CheckLuaParamCount(L, 0, callStr, paramsStr)) then begin at:= -1; with CurrentHedgehog^ do @@ -584,35 +610,76 @@ lc_hidemission:= 0; end; +function lc_getenginestring(L : Plua_state) : LongInt; Cdecl; +var stringType: shortstring; + msgId: LongInt; +const callStr = 'GetEngineString'; + paramsStr = 'stringType, msgId'; +begin + if CheckLuaParamCount(L, 2, callStr, paramsStr) then + begin + stringType:= lua_tostring(L, 1); + if (not lua_isnumber(L, 2)) then + begin + LuaError('Argument ''msgId'' must be a number!'); + lua_pushnil(L); + end + else if stringType = 'TMsgStrId' then + begin + msgId:= LuaToMsgStrIdOrd(L, 2, callStr, paramsStr); + if msgId = -1 then + lua_pushnil(L) + else + lua_pushstring(L, PChar(trmsg[TMsgStrId(msgId)])) + end + else if stringType = 'TGoalStrId' then + begin + msgId:= LuaToGoalStrIdOrd(L, 2, callStr, paramsStr); + if msgId = -1 then + lua_pushnil(L) + else + lua_pushstring(L, PChar(trgoal[TGoalStrId(msgId)])); + end + else + begin + LuaError('Invalid stringType!'); + lua_pushnil(L); + end + end + else + lua_pushnil(L); + lc_getenginestring:= 1; +end; + function lc_setammotexts(L : Plua_State) : LongInt; Cdecl; const - call = 'SetAmmoTexts'; - params = 'ammoType, name, caption, description [, showExtra]'; + callStr = 'SetAmmoTexts'; + paramsStr = 'ammoType, name, caption, description [, showExtra]'; var n: integer; showExtra: boolean; begin - if CheckAndFetchParamCount(L, 4, 5, call, params, n) then + if CheckAndFetchParamCount(L, 4, 5, callStr, paramsStr, n) then begin if n = 5 then showExtra:= lua_toboolean(L, 5) else showExtra:= true; - SetAmmoTexts(TAmmoType(LuaToAmmoTypeOrd(L, 1, call, params)), lua_tostringA(L, 2), lua_tostringA(L, 3), lua_tostringA(L, 4), showExtra); + SetAmmoTexts(TAmmoType(LuaToAmmoTypeOrd(L, 1, callStr, paramsStr)), lua_tostringA(L, 2), lua_tostringA(L, 3), lua_tostringA(L, 4), showExtra); end; lc_setammotexts:= 0; end; function lc_setammodescriptionappendix(L : Plua_State) : LongInt; Cdecl; const - call = 'SetAmmoDescriptionAppendix'; - params = 'ammoType, descAppend'; + callStr = 'SetAmmoDescriptionAppendix'; + paramsStr = 'ammoType, descAppend'; var ammoType: TAmmoType; descAppend: ansistring; begin - if CheckLuaParamCount(L, 2, call, params) then + if CheckLuaParamCount(L, 2, callStr, paramsStr) then begin - ammoType := TAmmoType(LuaToAmmoTypeOrd(L, 1, call, params)); + ammoType := TAmmoType(LuaToAmmoTypeOrd(L, 1, callStr, paramsStr)); descAppend := lua_tostringA(L, 2); trluaammoa[Ammoz[ammoType].NameId] := descAppend; end; @@ -667,16 +734,16 @@ function lc_addcaption(L : Plua_State) : LongInt; Cdecl; var cg: LongInt; const - call = 'AddCaption'; - params = 'text [, color, captiongroup]'; -begin - if CheckAndFetchParamCount(L, 1, 3, call, params, cg) then + callStr = 'AddCaption'; + paramsStr = 'text [, color, captiongroup]'; +begin + if CheckAndFetchParamCount(L, 1, 3, callStr, paramsStr, cg) then begin if cg = 1 then AddCaption(lua_tostringA(L, 1), capcolDefault, capgrpMessage) else begin - cg:= LuaToCapGroupOrd(L, 3, call, params); + cg:= LuaToCapGroupOrd(L, 3, callStr, paramsStr); if cg >= 0 then AddCaption(lua_tostringA(L, 1), Trunc(lua_tonumber(L, 2)) shr 8, TCapGroup(cg)); end @@ -846,12 +913,12 @@ dx, dy: hwFloat; gt: TGearType; const - call = 'AddGear'; - params = 'x, y, gearType, state, dx, dy, timer'; -begin - if CheckLuaParamCount(L, 7, call, params) then + callStr = 'AddGear'; + paramsStr = 'x, y, gearType, state, dx, dy, timer'; +begin + if CheckLuaParamCount(L, 7, callStr, paramsStr) then begin - t:= LuaToGearTypeOrd(L, 3, call, params); + t:= LuaToGearTypeOrd(L, 3, callStr, paramsStr); if t >= 0 then begin gt:= TGearType(t); @@ -893,13 +960,13 @@ vgt: TVisualGearType; uid: Longword; const - call = 'AddVisualGear'; - params = 'x, y, visualGearType, state, critical [, layer]'; + callStr = 'AddVisualGear'; + paramsStr = 'x, y, visualGearType, state, critical [, layer]'; begin uid:= 0; - if CheckAndFetchParamCount(L, 5, 6, call, params, n) then + if CheckAndFetchParamCount(L, 5, 6, callStr, paramsStr, n) then begin - s:= LuaToVisualGearTypeOrd(L, 3, call, params); + s:= LuaToVisualGearTypeOrd(L, 3, callStr, paramsStr); if s >= 0 then begin vgt:= TVisualGearType(s); @@ -1816,12 +1883,12 @@ var gear : PGear; at, n, c: LongInt; const - call = 'AddAmmo'; - params = 'gearUid, ammoType [, ammoCount]'; -begin - if CheckAndFetchParamCount(L, 2, 3, call, params, n) then + callStr = 'AddAmmo'; + paramsStr = 'gearUid, ammoType [, ammoCount]'; +begin + if CheckAndFetchParamCount(L, 2, 3, callStr, paramsStr, n) then begin - at:= LuaToAmmoTypeOrd(L, 2, call, params); + at:= LuaToAmmoTypeOrd(L, 2, callStr, paramsStr); if (at >= 0) and (TAmmoType(at) <> amNothing) then begin gear:= GearByUID(Trunc(lua_tonumber(L, 1))); @@ -1845,15 +1912,15 @@ ammo : PAmmo; at : LongInt; const - call = 'GetAmmoCount'; - params = 'gearUid, ammoType'; -begin - if CheckLuaParamCount(L, 2, call, params) then + callStr = 'GetAmmoCount'; + paramsStr = 'gearUid, ammoType'; +begin + if CheckLuaParamCount(L, 2, callStr, paramsStr) then begin gear:= GearByUID(Trunc(lua_tonumber(L, 1))); if (gear <> nil) and (gear^.Hedgehog <> nil) then begin - at:= LuaToAmmoTypeOrd(L, 2, call, params); + at:= LuaToAmmoTypeOrd(L, 2, callStr, paramsStr); if at >= 0 then begin ammo:= GetAmmoEntry(gear^.Hedgehog^, TAmmoType(at)); @@ -1955,12 +2022,12 @@ var gear: PGear; t : LongInt; const - call = 'SetEffect'; - params = 'gearUid, effect, effectState'; -begin - if CheckLuaParamCount(L, 3, call, params) then + callStr = 'SetEffect'; + paramsStr = 'gearUid, effect, effectState'; +begin + if CheckLuaParamCount(L, 3, callStr, paramsStr) then begin - t:= LuaToHogEffectOrd(L, 2, call, params); + t:= LuaToHogEffectOrd(L, 2, callStr, paramsStr); if t >= 0 then begin gear := GearByUID(Trunc(lua_tonumber(L, 1))); @@ -1975,12 +2042,12 @@ var gear : PGear; t : LongInt; const - call = 'GetEffect'; - params = 'gearUid, effect'; -begin - if CheckLuaParamCount(L, 2, call, params) then + callStr = 'GetEffect'; + paramsStr = 'gearUid, effect'; +begin + if CheckLuaParamCount(L, 2, callStr, paramsStr) then begin - t:= LuaToHogEffectOrd(L, 2, call, params); + t:= LuaToHogEffectOrd(L, 2, callStr, paramsStr); if t >= 0 then begin gear:= GearByUID(Trunc(lua_tonumber(L, 1))); @@ -2068,10 +2135,10 @@ function lc_endturn(L : Plua_State) : LongInt; Cdecl; var n: LongInt; const - call = 'EndTurn'; - params = '[noTaunts]'; -begin - if CheckAndFetchParamCount(L, 0, 1, call, params, n) then + callStr = 'EndTurn'; + paramsStr = '[noTaunts]'; +begin + if CheckAndFetchParamCount(L, 0, 1, callStr, paramsStr, n) then if n >= 1 then LuaNoEndTurnTaunts:= lua_toboolean(L, 1); LuaEndTurnRequested:= true; @@ -2082,10 +2149,10 @@ var n, time: LongInt; respectFactor: Boolean; const - call = 'Retreat'; - params = 'time [, respectGetAwayTimeFactor]'; -begin - if CheckAndFetchParamCount(L, 1, 2, call, params, n) then + callStr = 'Retreat'; + paramsStr = 'time [, respectGetAwayTimeFactor]'; +begin + if CheckAndFetchParamCount(L, 1, 2, callStr, paramsStr, n) then begin IsGetAwayTime:= true; AttackBar:= 0; @@ -2121,12 +2188,12 @@ color, tn: shortstring; needsTn : boolean; const - call = 'SendStat'; - params = 'statInfoType, color [, teamname]'; -begin - if CheckAndFetchParamCount(L, 2, 3, call, params, n) then + callStr = 'SendStat'; + paramsStr = 'statInfoType, color [, teamname]'; +begin + if CheckAndFetchParamCount(L, 2, 3, callStr, paramsStr, n) then begin - i:= LuaToStatInfoTypeOrd(L, 1, call, params); + i:= LuaToStatInfoTypeOrd(L, 1, callStr, paramsStr); if i >= 0 then begin statInfo:= TStatInfoType(i); @@ -2135,9 +2202,9 @@ if (n = 3) <> needsTn then begin if n = 3 then - LuaCallError(EnumToStr(statInfo) + ' does not support the teamname parameter', call, params) + LuaCallError(EnumToStr(statInfo) + ' does not support the teamname parameter', callStr, paramsStr) else - LuaCallError(EnumToStr(statInfo) + ' requires the teamname parameter', call, params); + LuaCallError(EnumToStr(statInfo) + ' requires the teamname parameter', callStr, paramsStr); end else // count is correct! begin @@ -2239,12 +2306,12 @@ n, s: LongInt; instaVoice: boolean; const - call = 'PlaySound'; - params = 'soundId [, hhGearUid [, instaVoice]]'; -begin - if CheckAndFetchParamCountRange(L, 1, 3, call, params, n) then + callStr = 'PlaySound'; + paramsStr = 'soundId [, hhGearUid [, instaVoice]]'; +begin + if CheckAndFetchParamCountRange(L, 1, 3, callStr, paramsStr, n) then begin - s:= LuaToSoundOrd(L, 1, call, params); + s:= LuaToSoundOrd(L, 1, callStr, paramsStr); if s >= 0 then begin // no gear specified @@ -2272,12 +2339,12 @@ function lc_playmusicsound(L : Plua_State) : LongInt; Cdecl; var s: LongInt; const - call = 'PlayMusicSound'; - params = 'soundId'; -begin - if CheckLuaParamCount(L, 1, call, params) then + callStr = 'PlayMusicSound'; + paramsStr = 'soundId'; +begin + if CheckLuaParamCount(L, 1, callStr, paramsStr) then begin - s:= LuaToSoundOrd(L, 1, call, params); + s:= LuaToSoundOrd(L, 1, callStr, paramsStr); if s >= 0 then PlayMusicSound(TSound(s)) end; @@ -2287,12 +2354,12 @@ function lc_stopmusicsound(L : Plua_State) : LongInt; Cdecl; var s: LongInt; const - call = 'StopMusicSound'; - params = 'soundId'; -begin - if CheckLuaParamCount(L, 1, call, params) then + callStr = 'StopMusicSound'; + paramsStr = 'soundId'; +begin + if CheckLuaParamCount(L, 1, callStr, paramsStr) then begin - s:= LuaToSoundOrd(L, 1, call, params); + s:= LuaToSoundOrd(L, 1, callStr, paramsStr); if s >= 0 then StopMusicSound(TSound(s)) end; @@ -2304,12 +2371,12 @@ var s: LongInt; soundState: boolean; const - call = 'SetSoundMask'; - params = 'soundId, isMasked'; -begin - if CheckLuaParamCount(L, 2, call, params) then + callStr = 'SetSoundMask'; + paramsStr = 'soundId, isMasked'; +begin + if CheckLuaParamCount(L, 2, callStr, paramsStr) then begin - s:= LuaToSoundOrd(L, 1, call, params); + s:= LuaToSoundOrd(L, 1, callStr, paramsStr); if s <> Ord(sndNone) then begin soundState:= lua_toboolean(L, 2); @@ -2858,12 +2925,12 @@ function lc_setammo(L : Plua_State) : LongInt; Cdecl; var np, at: LongInt; const - call = 'SetAmmo'; - params = 'ammoType, count, probability, delay [, numberInCrate]'; -begin - if CheckAndFetchParamCount(L, 4, 5, call, params, np) then + callStr = 'SetAmmo'; + paramsStr = 'ammoType, count, probability, delay [, numberInCrate]'; +begin + if CheckAndFetchParamCount(L, 4, 5, callStr, paramsStr, np) then begin - at:= LuaToAmmoTypeOrd(L, 1, call, params); + at:= LuaToAmmoTypeOrd(L, 1, callStr, paramsStr); if at >= 0 then begin if np = 4 then @@ -2879,13 +2946,13 @@ function lc_getammo(L : Plua_State) : LongInt; Cdecl; var i, at, rawProb, probLevel: LongInt; const - call = 'GetAmmo'; - params = 'ammoType'; + callStr = 'GetAmmo'; + paramsStr = 'ammoType'; begin lc_getammo:= 0; - if CheckLuaParamCount(L, 1, call, params) then + if CheckLuaParamCount(L, 1, callStr, paramsStr) then begin - at:= LuaToAmmoTypeOrd(L, 1, call, params); + at:= LuaToAmmoTypeOrd(L, 1, callStr, paramsStr); if at >= 0 then begin // Ammo count @@ -2913,12 +2980,12 @@ function lc_setammodelay(L : Plua_State) : LongInt; Cdecl; var at, delay: LongInt; const - call = 'SetAmmoDelay'; - params = 'ammoType, delay'; -begin - if CheckLuaParamCount(L, 2, call, params) then + callStr = 'SetAmmoDelay'; + paramsStr = 'ammoType, delay'; +begin + if CheckLuaParamCount(L, 2, callStr, paramsStr) then begin - at:= LuaToAmmoTypeOrd(L, 1, call, params); + at:= LuaToAmmoTypeOrd(L, 1, callStr, paramsStr); delay:= Trunc(lua_tonumber(L, 2)); if (at >= 0) and (TAmmoType(at) <> amNothing) then begin @@ -3065,11 +3132,11 @@ i, n : LongInt; placed, behind, flipHoriz, flipVert : boolean; const - call = 'PlaceSprite'; - params = 'x, y, sprite, frameIdx, tint, behind, flipHoriz, flipVert [, landFlag, ... ]'; + callStr = 'PlaceSprite'; + paramsStr = 'x, y, sprite, frameIdx, tint, behind, flipHoriz, flipVert [, landFlag, ... ]'; begin placed:= false; - if CheckAndFetchLuaParamMinCount(L, 4, call, params, n) then + if CheckAndFetchLuaParamMinCount(L, 4, callStr, paramsStr, n) then begin if not lua_isnoneornil(L, 5) then tint := Trunc(lua_tonumber(L, 5)) @@ -3089,12 +3156,12 @@ for i:= 9 to n do lf:= lf or Trunc(lua_tonumber(L, i)); - n:= LuaToSpriteOrd(L, 3, call, params); + n:= LuaToSpriteOrd(L, 3, callStr, paramsStr); if n >= 0 then begin spr:= TSprite(n); if SpritesData[spr].Surface = nil then - LuaError(call + ': ' + EnumToStr(spr) + ' cannot be placed! (required information not loaded)' ) + LuaError(callStr + ': ' + EnumToStr(spr) + ' cannot be placed! (required information not loaded)' ) else placed:= ForcePlaceOnLand( Trunc(lua_tonumber(L, 1)) - SpritesData[spr].Width div 2, @@ -3113,10 +3180,10 @@ i, n : LongInt; eraseOnLFMatch, onlyEraseLF, flipHoriz, flipVert : boolean; const - call = 'EraseSprite'; - params = 'x, y, sprite, frameIdx, eraseOnLFMatch, onlyEraseLF, flipHoriz, flipVert [, landFlag, ... ]'; -begin - if CheckAndFetchLuaParamMinCount(L, 4, call, params, n) then + callStr = 'EraseSprite'; + paramsStr = 'x, y, sprite, frameIdx, eraseOnLFMatch, onlyEraseLF, flipHoriz, flipVert [, landFlag, ... ]'; +begin + if CheckAndFetchLuaParamMinCount(L, 4, callStr, paramsStr, n) then begin if not lua_isnoneornil(L, 5) then eraseOnLFMatch := lua_toboolean(L, 5) @@ -3136,12 +3203,12 @@ for i:= 9 to n do lf:= lf or Trunc(lua_tonumber(L, i)); - n:= LuaToSpriteOrd(L, 3, call, params); + n:= LuaToSpriteOrd(L, 3, callStr, paramsStr); if n >= 0 then begin spr:= TSprite(n); if SpritesData[spr].Surface = nil then - LuaError(call + ': ' + EnumToStr(spr) + ' cannot be placed! (required information not loaded)' ) + LuaError(callStr + ': ' + EnumToStr(spr) + ' cannot be placed! (required information not loaded)' ) else EraseLand( Trunc(lua_tonumber(L, 1)) - SpritesData[spr].Width div 2, @@ -3225,7 +3292,7 @@ if CheckLuaParamCount(L, 1, 'HideHog', 'gearUid') then begin gear:= GearByUID(Trunc(lua_tonumber(L, 1))); - if (gear <> nil) and (gear^.hedgehog <> nil) then + if (gear <> nil) and (gear^.hedgehog <> nil) and (gear^.hedgehog^.gear <> nil) then begin HideHog(gear^.hedgehog); lua_pushboolean(L, true); @@ -3388,12 +3455,12 @@ function lc_getammoname(L : Plua_state) : LongInt; Cdecl; var np, at: LongInt; ignoreOverwrite: Boolean; -const call = 'GetAmmoName'; - params = 'ammoType [, ignoreOverwrite ]'; -begin - if CheckAndFetchParamCountRange(L, 1, 2, call, params, np) then +const callStr = 'GetAmmoName'; + paramsStr = 'ammoType [, ignoreOverwrite ]'; +begin + if CheckAndFetchParamCountRange(L, 1, 2, callStr, paramsStr, np) then begin - at:= LuaToAmmoTypeOrd(L, 1, call, params); + at:= LuaToAmmoTypeOrd(L, 1, callStr, paramsStr); ignoreOverwrite := false; if np > 1 then ignoreOverwrite := lua_toboolean(L, 2); @@ -3412,15 +3479,15 @@ var at: LongInt; weapon: PAmmo; gear: PGear; -const call = 'GetAmmoTimer'; - params = 'gearUid, ammoType'; -begin - if CheckLuaParamCount(L, 2, call, params) then +const callStr = 'GetAmmoTimer'; + paramsStr = 'gearUid, ammoType'; +begin + if CheckLuaParamCount(L, 2, callStr, paramsStr) then begin gear:= GearByUID(Trunc(lua_tonumber(L, 1))); if (gear <> nil) and (gear^.Hedgehog <> nil) then begin - at:= LuaToAmmoTypeOrd(L, 2, call, params); + at:= LuaToAmmoTypeOrd(L, 2, callStr, paramsStr); weapon:= GetAmmoEntry(gear^.Hedgehog^, TAmmoType(at)); if (Ammoz[TAmmoType(at)].Ammo.Propz and ammoprop_Timerable) <> 0 then lua_pushnumber(L, weapon^.Timer) @@ -3600,10 +3667,10 @@ function lc_endluatest(L : Plua_State) : LongInt; Cdecl; var rstring: shortstring; const - call = 'EndLuaTest'; - params = 'TEST_SUCCESSFUL or TEST_FAILED'; -begin - if CheckLuaParamCount(L, 1, call, params) then + callStr = 'EndLuaTest'; + paramsStr = 'TEST_SUCCESSFUL or TEST_FAILED'; +begin + if CheckLuaParamCount(L, 1, callStr, paramsStr) then begin case Trunc(lua_tonumber(L, 1)) of @@ -3611,7 +3678,7 @@ HaltTestFailed: rstring:= 'FAILED'; else begin - LuaCallError('Parameter must be either ' + params, call, params); + LuaCallError('Parameter must be either ' + paramsStr, callStr, paramsStr); exit(0); end; end; @@ -4257,6 +4324,8 @@ spr: TSprite; mg : TMapGen; we : TWorldEdge; + msi: TMsgStrId; + gsi: TGoalStrId; begin // initialize lua luaState:= lua_open; @@ -4375,6 +4444,13 @@ for we:= Low(TWorldEdge) to High(TWorldEdge) do ScriptSetInteger(EnumToStr(we), ord(we)); +// register message IDs +for msi:= Low(TMsgStrId) to High(TMsgStrId) do + ScriptSetInteger(EnumToStr(msi), ord(msi)); + +for gsi:= Low(TGoalStrId) to High(TGoalStrId) do + ScriptSetInteger(EnumToStr(gsi), ord(gsi)); + ScriptSetLongWord('capcolDefault' , capcolDefaultLua); ScriptSetLongWord('capcolSetting' , capcolSettingLua); @@ -4485,6 +4561,7 @@ lua_register(luaState, _P'ParseCommand', @lc_parsecommand); lua_register(luaState, _P'ShowMission', @lc_showmission); lua_register(luaState, _P'HideMission', @lc_hidemission); +lua_register(luaState, _P'GetEngineString', @lc_getenginestring); lua_register(luaState, _P'SetAmmoTexts', @lc_setammotexts); lua_register(luaState, _P'SetAmmoDescriptionAppendix', @lc_setammodescriptionappendix); lua_register(luaState, _P'AddCaption', @lc_addcaption); diff -r 772a43d88e6b -r 2146cb7be36f hedgewars/uStore.pas --- a/hedgewars/uStore.pas Fri Mar 10 11:42:25 2023 +0100 +++ b/hedgewars/uStore.pas Tue Aug 22 08:35:46 2023 +0200 @@ -474,12 +474,30 @@ end; if (ii in [sprAMAmmos, sprAMAmmosBW]) then begin + // Optionally add ammos overlay from HWP file tmpoverlay := LoadDataImage(Path, copy(FileName, 1, length(FileName)-5), (imflags and (not ifCritical))); if tmpoverlay <> nil then begin copyToXY(tmpoverlay, tmpsurf, 0, 0); SDL_FreeSurface(tmpoverlay) - end + end; + + // Replace ExtraDamage icon with a variant showing "1,5" instead of "1.5" + // if the current locale uses a comma as a decimal separator. + if lDecimalSeparator = ',' then + begin + if ii = sprAMAmmos then + tmpoverlay:= LoadDataImage(ptAmmoMenu, 'Ammos_ExtraDamage_comma', ifNone) + else + tmpoverlay:= LoadDataImage(ptAmmoMenu, 'Ammos_bw_ExtraDamage_comma', ifNone); + if tmpoverlay <> nil then + begin + copyToXY(tmpoverlay, tmpsurf, + GetSurfaceFrameCoordinateX(tmpsurf, ord(amExtraDamage)-1, SpritesData[ii].Width, SpritesData[ii].Height), + GetSurfaceFrameCoordinateY(tmpsurf, ord(amExtraDamage)-1, SpritesData[ii].Height)); + SDL_FreeSurface(tmpoverlay); + end; + end; end; if (ii in [sprSky, sprSkyL, sprSkyR, sprHorizont, sprHorizontL, sprHorizontR]) then begin diff -r 772a43d88e6b -r 2146cb7be36f hedgewars/uUtils.pas --- a/hedgewars/uUtils.pas Fri Mar 10 11:42:25 2023 +0100 +++ b/hedgewars/uUtils.pas Tue Aug 22 08:35:46 2023 +0200 @@ -34,6 +34,9 @@ procedure EscapeCharA(var a: ansistring; e: char); procedure UnEscapeCharA(var a: ansistring; e: char); +procedure ReplaceChars(var a: shortstring; c1, c2: char); +procedure ReplaceCharsA(var a: ansistring; c1, c2: char); + function ExtractFileDir(s: shortstring) : shortstring; function ExtractFileName(s: shortstring) : shortstring; @@ -47,6 +50,8 @@ function EnumToStr(const en : TSprite) : shortstring; overload; function EnumToStr(const en : TMapGen) : shortstring; overload; function EnumToStr(const en : TWorldEdge) : shortstring; overload; +function EnumToStr(const en : TMsgStrId) : shortstring; overload; +function EnumToStr(const en : TGoalStrId) : shortstring; overload; function Min(a, b: LongInt): LongInt; function MinD(a, b: double) : double; @@ -304,6 +309,28 @@ until (i <= 0); end; { UnEscapeCharA } +// Replace all characters c1 with c2 in shortstring a +procedure ReplaceChars(var a: shortstring; c1, c2: char); +var i: LongInt; +begin +repeat + i:= Pos(c1, a); + if (i > 0) then + a[i]:= c2; +until (i <= 0); +end; { ReplaceChars } + +// Replace all characters c1 with c2 in antistring a +procedure ReplaceCharsA(var a: ansistring; c1, c2: char); +var i: LongInt; +begin +repeat + i:= Pos(c1, a); + if (i > 0) then + a[i]:= c2; +until (i <= 0); +end; { ReplaceCharsA } + function EnumToStr(const en : TGearType) : shortstring; overload; begin EnumToStr:= GetEnumName(TypeInfo(TGearType), ord(en)) @@ -354,6 +381,15 @@ EnumToStr := GetEnumName(TypeInfo(TWorldEdge), ord(en)) end; +function EnumToStr(const en: TMsgStrId) : shortstring; overload; +begin +EnumToStr := GetEnumName(TypeInfo(TMsgStrId), ord(en)) +end; + +function EnumToStr(const en: TGoalStrId) : shortstring; overload; +begin +EnumToStr := GetEnumName(TypeInfo(TGoalStrId), ord(en)) +end; function Min(a, b: LongInt): LongInt; begin diff -r 772a43d88e6b -r 2146cb7be36f hedgewars/uVariables.pas --- a/hedgewars/uVariables.pas Fri Mar 10 11:42:25 2023 +0100 +++ b/hedgewars/uVariables.pas Tue Aug 22 08:35:46 2023 +0200 @@ -357,36 +357,36 @@ const FontzInit: array[THWFont] of THHFont = ( (Handle: nil; - Height: 12*HDPIScaleFactor; + Height: round(12*HDPIScaleFactor); style: TTF_STYLE_NORMAL; Name: 'DejaVuSans-Bold.ttf'), (Handle: nil; - Height: 24*HDPIScaleFactor; + Height: round(24*HDPIScaleFactor); style: TTF_STYLE_NORMAL; Name: 'DejaVuSans-Bold.ttf'), (Handle: nil; - Height: 10*HDPIScaleFactor; + Height: round(10*HDPIScaleFactor); style: TTF_STYLE_NORMAL; Name: 'DejaVuSans-Bold.ttf'), (Handle: nil; // fntChat - Height: 12*HDPIScaleFactor; + Height: round(12*HDPIScaleFactor); style: TTF_STYLE_NORMAL; Name: 'DejaVuSans-Bold.ttf') {$IFNDEF MOBILE}, // remove chinese fonts for now (Handle: nil; - Height: 12*HDPIScaleFactor; + Height: round(12*HDPIScaleFactor); style: TTF_STYLE_NORMAL; Name: 'wqy-zenhei.ttc'), (Handle: nil; - Height: 24*HDPIScaleFactor; + Height: round(24*HDPIScaleFactor); style: TTF_STYLE_NORMAL; Name: 'wqy-zenhei.ttc'), (Handle: nil; - Height: 10*HDPIScaleFactor; + Height: round(10*HDPIScaleFactor); style: TTF_STYLE_NORMAL; Name: 'wqy-zenhei.ttc'), (Handle: nil; // CJKfntChat - Height: 12*HDPIScaleFactor; + Height: round(12*HDPIScaleFactor); style: TTF_STYLE_NORMAL; Name: 'wqy-zenhei.ttc') {$ENDIF} diff -r 772a43d88e6b -r 2146cb7be36f misc/libphyslayer/physfscompat.h --- a/misc/libphyslayer/physfscompat.h Fri Mar 10 11:42:25 2023 +0100 +++ b/misc/libphyslayer/physfscompat.h Tue Aug 22 08:35:46 2023 +0200 @@ -21,6 +21,10 @@ #include "physfs.h" +#if defined(_WIN32) +#define PHYSFS_DECL __declspec(dllexport) +#endif + #if PHYSFS_VER_MAJOR == 2 #if PHYSFS_VER_MINOR == 0 diff -r 772a43d88e6b -r 2146cb7be36f project_files/HedgewarsMobile/Classes/SupportViewController.m --- a/project_files/HedgewarsMobile/Classes/SupportViewController.m Fri Mar 10 11:42:25 2023 +0100 +++ b/project_files/HedgewarsMobile/Classes/SupportViewController.m Tue Aug 22 08:35:46 2023 +0200 @@ -131,7 +131,7 @@ urlString = @"https://www.hedgewars.org"; break; case 3: - urlString = @"https://webchat.freenode.net/?channels=hedgewars"; + urlString = @"https://web.libera.chat/#hedgewars"; break; default: DLog(@"No way"); diff -r 772a43d88e6b -r 2146cb7be36f project_files/hwc/CMakeLists.txt --- a/project_files/hwc/CMakeLists.txt Fri Mar 10 11:42:25 2023 +0100 +++ b/project_files/hwc/CMakeLists.txt Tue Aug 22 08:35:46 2023 +0200 @@ -112,7 +112,7 @@ ${LUA_LIBRARY} ${OPENGL_LIBRARY} ${SDL2_LIBRARIES} - ${SDL2_MIXER_LIBRARIES} + ${SDL2_MIXER_LIBRARY} ${SDL2_NET_LIBRARIES} ${SDL2_IMAGE_LIBRARIES} ${SDL2_TTF_LIBRARIES} diff -r 772a43d88e6b -r 2146cb7be36f rust/hedgewars-server/Cargo.toml --- a/rust/hedgewars-server/Cargo.toml Fri Mar 10 11:42:25 2023 +0100 +++ b/rust/hedgewars-server/Cargo.toml Tue Aug 22 08:35:46 2023 +0200 @@ -5,7 +5,8 @@ authors = [ "Andrey Korotaev " ] [features] -official-server = ["mysql_async", "sha1"] +tls-connections = ["tokio-native-tls"] +official-server = ["mysql_async", "sha1", "tls-connections"] default = [] [dependencies] @@ -25,6 +26,7 @@ sha1 = { version = "0.10.0", optional = true } slab = "0.4" tokio = { version = "1.16", features = ["full"]} +tokio-native-tls = { version = "0.3", optional = true } hedgewars-network-protocol = { path = "../hedgewars-network-protocol" } diff -r 772a43d88e6b -r 2146cb7be36f rust/hedgewars-server/src/core.rs --- a/rust/hedgewars-server/src/core.rs Fri Mar 10 11:42:25 2023 +0100 +++ b/rust/hedgewars-server/src/core.rs Tue Aug 22 08:35:46 2023 +0200 @@ -1,6 +1,5 @@ pub mod anteroom; pub mod client; -pub mod events; pub mod indexslab; pub mod room; pub mod server; diff -r 772a43d88e6b -r 2146cb7be36f rust/hedgewars-server/src/core/anteroom.rs --- a/rust/hedgewars-server/src/core/anteroom.rs Fri Mar 10 11:42:25 2023 +0100 +++ b/rust/hedgewars-server/src/core/anteroom.rs Tue Aug 22 08:35:46 2023 +0200 @@ -32,6 +32,7 @@ impl BanCollection { fn new() -> Self { + todo!("add nick bans"); Self { ban_ips: vec![], ban_timeouts: vec![], diff -r 772a43d88e6b -r 2146cb7be36f rust/hedgewars-server/src/core/client.rs --- a/rust/hedgewars-server/src/core/client.rs Fri Mar 10 11:42:25 2023 +0100 +++ b/rust/hedgewars-server/src/core/client.rs Tue Aug 22 08:35:46 2023 +0200 @@ -10,6 +10,7 @@ const IS_CONTRIBUTOR = 0b0001_0000; const HAS_SUPER_POWER = 0b0010_0000; const IS_REGISTERED = 0b0100_0000; + const IS_MODERATOR = 0b1000_0000; const NONE = 0b0000_0000; const DEFAULT = Self::NONE.bits; @@ -29,6 +30,7 @@ impl HwClient { pub fn new(id: ClientId, protocol_number: u16, nick: String) -> HwClient { + todo!("add quiet flag"); HwClient { id, nick, diff -r 772a43d88e6b -r 2146cb7be36f rust/hedgewars-server/src/core/events.rs --- a/rust/hedgewars-server/src/core/events.rs Fri Mar 10 11:42:25 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,116 +0,0 @@ -use slab::Slab; -use std::{ - convert::TryInto, - iter, - num::NonZeroU32, - time::{Duration, Instant}, -}; - -struct Event { - event_id: u32, - data: Data, -} - -#[derive(Clone)] -pub struct Timeout { - tick_index: u32, - event_index: u32, - event_id: u32, -} - -pub struct TimedEvents { - events: [Slab>; MAX_TIMEOUT], - current_time: Instant, - current_tick_index: u32, - next_event_id: u32, - events_count: u32, -} - -impl TimedEvents { - pub fn new() -> Self { - Self { - events: [0; MAX_TIMEOUT].map(|_| Slab::new()), - current_time: Instant::now(), - current_tick_index: 0, - next_event_id: 0, - events_count: 0, - } - } - - pub fn set_timeout(&mut self, seconds_delay: NonZeroU32, data: Data) -> Timeout { - let tick_index = (self.current_tick_index - + std::cmp::min(seconds_delay.get(), MAX_TIMEOUT as u32)) - % MAX_TIMEOUT as u32; - let event_id = self.next_event_id; - self.next_event_id += 1; - let event = Event { event_id, data }; - - let entry = self.events[tick_index as usize].vacant_entry(); - let event_index = entry.key() as u32; - entry.insert(event); - - self.events_count += 1; - - Timeout { - tick_index, - event_index, - event_id, - } - } - - pub fn cancel_timeout(&mut self, timeout: Timeout) -> Option { - let events = &mut self.events[timeout.tick_index as usize]; - if matches!(events.get(timeout.event_index as usize), Some(Event { event_id: id, ..}) if *id == timeout.event_id) - { - self.events_count -= 1; - Some(events.remove(timeout.event_index as usize).data) - } else { - None - } - } - - pub fn poll(&mut self, time: Instant) -> Vec { - let mut result = vec![]; - let second = Duration::from_secs(1); - while time - self.current_time > second { - self.current_time += second; - self.current_tick_index = (self.current_tick_index + 1) % MAX_TIMEOUT as u32; - result.extend( - self.events[self.current_tick_index as usize] - .drain() - .map(|e| e.data), - ); - } - self.events_count -= result.len() as u32; - result - } - - pub fn is_empty(&self) -> bool { - self.events_count == 0 - } -} - -mod test { - use super::TimedEvents; - use std::{ - num::NonZeroU32, - time::{Duration, Instant}, - }; - - #[test] - fn events_test() { - let mut events = TimedEvents::::new(); - let now = Instant::now(); - - let timeouts = (1..=3) - .map(|n| events.set_timeout(NonZeroU32::new(n).unwrap(), n)) - .collect::>(); - - let second = Duration::from_secs(1); - assert_eq!(events.cancel_timeout(timeouts[1].clone()), Some(2)); - assert_eq!(events.poll(now + second), vec![1]); - assert!(events.poll(now + second).is_empty()); - assert!(events.poll(now + 2 * second).is_empty()); - assert_eq!(events.poll(now + 3 * second), vec![3]); - } -} diff -r 772a43d88e6b -r 2146cb7be36f rust/hedgewars-server/src/core/server.rs --- a/rust/hedgewars-server/src/core/server.rs Fri Mar 10 11:42:25 2023 +0100 +++ b/rust/hedgewars-server/src/core/server.rs Tue Aug 22 08:35:46 2023 +0200 @@ -189,6 +189,7 @@ impl HwServer { pub fn new(clients_limit: usize, rooms_limit: usize) -> Self { + todo!("add reconnection IDs"); let rooms = Slab::with_capacity(rooms_limit); let clients = IndexSlab::with_capacity(clients_limit); let checkers = IndexSlab::new(); diff -r 772a43d88e6b -r 2146cb7be36f rust/hedgewars-server/src/handlers/common.rs --- a/rust/hedgewars-server/src/handlers/common.rs Fri Mar 10 11:42:25 2023 +0100 +++ b/rust/hedgewars-server/src/handlers/common.rs Tue Aug 22 08:35:46 2023 +0200 @@ -514,6 +514,7 @@ result: Result, response: &mut super::Response, ) { + todo!("voting result needs to be processed with raised privileges"); let room_id = room_control.room().id; super::common::get_vote_data(room_control.room().id, &result, response); diff -r 772a43d88e6b -r 2146cb7be36f rust/hedgewars-server/src/handlers/inanteroom.rs --- a/rust/hedgewars-server/src/handlers/inanteroom.rs Fri Mar 10 11:42:25 2023 +0100 +++ b/rust/hedgewars-server/src/handlers/inanteroom.rs Tue Aug 22 08:35:46 2023 +0200 @@ -13,8 +13,6 @@ HwProtocolMessage, HwProtocolMessage::LoadRoom, HwServerMessage::*, }; use log::*; -#[cfg(feature = "official-server")] -use openssl::sha::sha1; use std::{ fmt::{Formatter, LowerHex}, num::NonZeroU16, diff -r 772a43d88e6b -r 2146cb7be36f rust/hedgewars-server/src/handlers/inlobby.rs --- a/rust/hedgewars-server/src/handlers/inlobby.rs Fri Mar 10 11:42:25 2023 +0100 +++ b/rust/hedgewars-server/src/handlers/inlobby.rs Tue Aug 22 08:35:46 2023 +0200 @@ -26,6 +26,8 @@ ) { use hedgewars_network_protocol::messages::HwProtocolMessage::*; + todo!("add kick/ban handlers"); + match message { CreateRoom(name, password) => match server.create_room(client_id, name, password) { Err(CreateRoomError::InvalidName) => response.warn(ILLEGAL_ROOM_NAME), @@ -47,6 +49,7 @@ } }, Chat(msg) => { + todo!("add client quiet flag"); response.add( ChatMsg { nick: server.client(client_id).nick.clone(), diff -r 772a43d88e6b -r 2146cb7be36f rust/hedgewars-server/src/main.rs --- a/rust/hedgewars-server/src/main.rs Fri Mar 10 11:42:25 2023 +0100 +++ b/rust/hedgewars-server/src/main.rs Tue Aug 22 08:35:46 2023 +0200 @@ -27,6 +27,7 @@ let args: Vec = env::args().collect(); let mut opts = Options::new(); + todo!("Add options for cert paths"); opts.optopt("p", "port", "port - defaults to 46631", "PORT"); opts.optflag("h", "help", "help"); let matches = match opts.parse(&args[1..]) { diff -r 772a43d88e6b -r 2146cb7be36f rust/hedgewars-server/src/server/database.rs --- a/rust/hedgewars-server/src/server/database.rs Fri Mar 10 11:42:25 2023 +0100 +++ b/rust/hedgewars-server/src/server/database.rs Tue Aug 22 08:35:46 2023 +0200 @@ -1,5 +1,6 @@ use mysql_async::{self, from_row_opt, params, prelude::*, Pool}; use sha1::{Digest, Sha1}; +use tokio::sync::mpsc::{channel, Receiver, Sender}; use crate::handlers::{AccountInfo, Sha1Digest}; @@ -25,14 +26,96 @@ pub struct Achievements {} +pub enum DatabaseQuery { + CheckRegistered { + nick: String, + }, + GetAccount { + nick: String, + protocol: u16, + password_hash: String, + client_salt: String, + server_salt: String, + }, + GetCheckerAccount { + nick: String, + password: String, + }, + GetReplayFilename { + id: u32, + }, +} + +pub enum DatabaseResponse { + AccountRegistered(bool), + Account(Option), + CheckerAccount { is_registered: bool }, +} + pub struct Database { pool: Pool, + query_rx: Receiver, + response_tx: Sender, } impl Database { pub fn new(url: &str) -> Self { + let (query_tx, query_rx) = channel(32); + let (response_tx, response_rx) = channel(32); Self { pool: Pool::new(url), + query_rx, + response_tx, + } + } + + pub async fn run(&mut self) { + use DatabaseResponse::*; + loop { + let query = self.query_rx.recv().await; + if let Some(query) = query { + match query { + DatabaseQuery::CheckRegistered { nick } => { + let is_registered = self.get_is_registered(&nick).await.unwrap_or(false); + self.response_tx + .send(AccountRegistered(is_registered)) + .await; + } + DatabaseQuery::GetAccount { + nick, + protocol, + password_hash, + client_salt, + server_salt, + } => { + let account = self + .get_account( + &nick, + protocol, + &password_hash, + &client_salt, + &server_salt, + ) + .await + .unwrap_or(None); + self.response_tx.send(Account(account)).await; + } + DatabaseQuery::GetCheckerAccount { nick, password } => { + let is_registered = self + .get_checker_account(&nick, &password) + .await + .unwrap_or(false); + self.response_tx + .send(CheckerAccount { is_registered }) + .await; + } + DatabaseQuery::GetReplayFilename { id } => { + let filename = self.get_replay_name(id).await; + } + }; + } else { + break; + } } } @@ -40,9 +123,9 @@ let mut connection = self.pool.get_conn().await?; let result = CHECK_ACCOUNT_EXISTS_QUERY .with(params! { "username" => nick }) - .first(&mut connection) + .first::(&mut connection) .await?; - Ok(!result.is_empty()) + Ok(!result.is_some()) } pub async fn get_account( diff -r 772a43d88e6b -r 2146cb7be36f rust/hedgewars-server/src/server/demo.rs --- a/rust/hedgewars-server/src/server/demo.rs Fri Mar 10 11:42:25 2023 +0100 +++ b/rust/hedgewars-server/src/server/demo.rs Tue Aug 22 08:35:46 2023 +0200 @@ -135,6 +135,7 @@ let mut teams = vec![]; let mut hog_index = 7usize; + todo!("read messages from file"); let messages = vec![]; while let Some(cmd) = read_command(&mut reader, &mut buffer)? { diff -r 772a43d88e6b -r 2146cb7be36f rust/hedgewars-server/src/server/io.rs --- a/rust/hedgewars-server/src/server/io.rs Fri Mar 10 11:42:25 2023 +0100 +++ b/rust/hedgewars-server/src/server/io.rs Tue Aug 22 08:35:46 2023 +0200 @@ -2,6 +2,7 @@ fs::{File, OpenOptions}, io::{Error, ErrorKind, Read, Result, Write}, sync::{mpsc, Arc}, + task::Waker, thread, }; @@ -23,8 +24,9 @@ let (core_tx, io_rx) = mpsc::channel(); let (io_tx, core_rx) = mpsc::channel(); - let mut db = Database::new(); - db.connect("localhost"); + todo!("convert into an IO task"); + + /*let mut db = Database::new("localhost"); thread::spawn(move || { while let Ok((request_id, task)) = io_rx.recv() { @@ -138,7 +140,7 @@ io_tx.send((request_id, response)); waker.wake(); } - }); + });*/ Self { core_rx, core_tx } } diff -r 772a43d88e6b -r 2146cb7be36f rust/hedgewars-server/src/server/network.rs --- a/rust/hedgewars-server/src/server/network.rs Fri Mar 10 11:42:25 2023 +0100 +++ b/rust/hedgewars-server/src/server/network.rs Tue Aug 22 08:35:46 2023 +0200 @@ -1,22 +1,24 @@ use bytes::{Buf, Bytes}; use log::*; use slab::Slab; +use std::io::Error; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::{ iter::Iterator, net::{IpAddr, SocketAddr}, time::Duration, }; use tokio::{ - io::AsyncReadExt, + io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf}, net::{TcpListener, TcpStream}, sync::mpsc::{channel, Receiver, Sender}, }; +#[cfg(feature = "tls-connections")] +use tokio_native_tls::{TlsAcceptor, TlsStream}; use crate::{ - core::{ - events::{TimedEvents, Timeout}, - types::ClientId, - }, + core::types::ClientId, handlers, handlers::{IoResult, IoTask, ServerState}, protocol::{self, ProtocolDecoder, ProtocolError}, @@ -25,7 +27,6 @@ use hedgewars_network_protocol::{ messages::HwServerMessage::Redirect, messages::*, parser::server_message, }; -use tokio::io::AsyncWriteExt; const PING_TIMEOUT: Duration = Duration::from_secs(15); @@ -56,9 +57,65 @@ } } +enum ClientStream { + Tcp(TcpStream), + #[cfg(feature = "tls-connections")] + Tls(TlsStream), +} + +impl Unpin for ClientStream {} + +impl AsyncRead for ClientStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + use ClientStream::*; + match Pin::into_inner(self) { + Tcp(stream) => Pin::new(stream).poll_read(cx, buf), + #[cfg(feature = "tls-connections")] + Tls(stream) => Pin::new(stream).poll_read(cx, buf), + } + } +} + +impl AsyncWrite for ClientStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + use ClientStream::*; + match Pin::into_inner(self) { + Tcp(stream) => Pin::new(stream).poll_write(cx, buf), + #[cfg(feature = "tls-connections")] + Tls(stream) => Pin::new(stream).poll_write(cx, buf), + } + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + use ClientStream::*; + match Pin::into_inner(self) { + Tcp(stream) => Pin::new(stream).poll_flush(cx), + #[cfg(feature = "tls-connections")] + Tls(stream) => Pin::new(stream).poll_flush(cx), + } + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + use ClientStream::*; + match Pin::into_inner(self) { + Tcp(stream) => Pin::new(stream).poll_shutdown(cx), + #[cfg(feature = "tls-connections")] + Tls(stream) => Pin::new(stream).poll_shutdown(cx), + } + } +} + struct NetworkClient { id: ClientId, - socket: TcpStream, + stream: ClientStream, receiver: Receiver, peer_addr: SocketAddr, decoder: ProtocolDecoder, @@ -67,27 +124,27 @@ impl NetworkClient { fn new( id: ClientId, - socket: TcpStream, + stream: ClientStream, peer_addr: SocketAddr, receiver: Receiver, ) -> Self { Self { id, - socket, + stream, peer_addr, receiver, decoder: ProtocolDecoder::new(PING_TIMEOUT), } } - async fn read( - socket: &mut TcpStream, + async fn read( + stream: &mut T, decoder: &mut ProtocolDecoder, ) -> protocol::Result { - let result = decoder.read_from(socket).await; + let result = decoder.read_from(stream).await; if matches!(result, Err(ProtocolError::Timeout)) { - if Self::write(socket, Bytes::from(HwServerMessage::Ping.to_raw_protocol())).await { - decoder.read_from(socket).await + if Self::write(stream, Bytes::from(HwServerMessage::Ping.to_raw_protocol())).await { + decoder.read_from(stream).await } else { Err(ProtocolError::Eof) } @@ -96,8 +153,8 @@ } } - async fn write(socket: &mut TcpStream, mut data: Bytes) -> bool { - !data.has_remaining() || matches!(socket.write_buf(&mut data).await, Ok(n) if n > 0) + async fn write(stream: &mut T, mut data: Bytes) -> bool { + !data.has_remaining() || matches!(stream.write_buf(&mut data).await, Ok(n) if n > 0) } async fn run(mut self, sender: Sender) { @@ -111,7 +168,7 @@ tokio::select! { server_message = self.receiver.recv() => { match server_message { - Some(message) => if !Self::write(&mut self.socket, message).await { + Some(message) => if !Self::write(&mut self.stream, message).await { sender.send(Error("Connection reset by peer".to_string())).await; break; } @@ -120,7 +177,7 @@ } } } - client_message = Self::read(&mut self.socket, &mut self.decoder) => { + client_message = Self::read(&mut self.stream, &mut self.decoder) => { match client_message { Ok(message) => { if !sender.send(Message(message)).await { @@ -128,9 +185,10 @@ } } Err(e) => { + todo!("send cmdline errors"); sender.send(Error(format!("{}", e))).await; if matches!(e, ProtocolError::Timeout) { - Self::write(&mut self.socket, Bytes::from(HwServerMessage::Bye("Ping timeout".to_string()).to_raw_protocol())).await; + Self::write(&mut self.stream, Bytes::from(HwServerMessage::Bye("Ping timeout".to_string()).to_raw_protocol())).await; } break; } @@ -141,8 +199,16 @@ } } +#[cfg(feature = "tls-connections")] +struct TlsListener { + listener: TcpListener, + acceptor: TlsAcceptor, +} + pub struct NetworkLayer { listener: TcpListener, + #[cfg(feature = "tls-connections")] + tls: TlsListener, server_state: ServerState, clients: Slab>, } @@ -151,36 +217,84 @@ pub async fn run(&mut self) { let (update_tx, mut update_rx) = channel(128); - loop { - tokio::select! { - Ok((stream, addr)) = self.listener.accept() => { - if let Some(client) = self.create_client(stream, addr).await { - tokio::spawn(client.run(update_tx.clone())); + async fn accept_plain_branch( + layer: &mut NetworkLayer, + value: (TcpStream, SocketAddr), + update_tx: Sender, + ) { + let (stream, addr) = value; + if let Some(client) = layer.create_client(ClientStream::Tcp(stream), addr).await { + tokio::spawn(client.run(update_tx)); + } + } + + #[cfg(feature = "tls-connections")] + async fn accept_tls_branch( + layer: &mut NetworkLayer, + value: (TcpStream, SocketAddr), + update_tx: Sender, + ) { + let (stream, addr) = value; + match layer.tls.acceptor.accept(stream).await { + Ok(stream) => { + if let Some(client) = layer.create_client(ClientStream::Tls(stream), addr).await + { + tokio::spawn(client.run(update_tx)); } } - client_message = update_rx.recv(), if !self.clients.is_empty() => { - use ClientUpdateData::*; - match client_message { - Some(ClientUpdate{ client_id, data: Message(message) } ) => { - self.handle_message(client_id, message).await; - } - Some(ClientUpdate{ client_id, data: Error(e) } ) => { - let mut response = handlers::Response::new(client_id); - info!("Client {} error: {:?}", client_id, e); - response.remove_client(client_id); - handlers::handle_client_loss(&mut self.server_state, client_id, &mut response); - self.handle_response(response).await; - } - None => unreachable!() - } + Err(e) => { + warn!("Unable to establish TLS connection: {}", e); + } + } + } + + async fn client_message_branch( + layer: &mut NetworkLayer, + client_message: Option, + ) { + use ClientUpdateData::*; + match client_message { + Some(ClientUpdate { + client_id, + data: Message(message), + }) => { + layer.handle_message(client_id, message).await; } + Some(ClientUpdate { + client_id, + data: Error(e), + }) => { + let mut response = handlers::Response::new(client_id); + info!("Client {} error: {:?}", client_id, e); + response.remove_client(client_id); + handlers::handle_client_loss(&mut layer.server_state, client_id, &mut response); + layer.handle_response(response).await; + } + None => unreachable!(), + } + } + + todo!("add the DB task"); + todo!("add certfile watcher task"); + loop { + #[cfg(not(feature = "tls-connections"))] + tokio::select! { + Ok(value) = self.listener.accept() => accept_plain_branch(self, value, update_tx.clone()).await, + client_message = update_rx.recv(), if !self.clients.is_empty() => client_message_branch(self, client_message).await + } + + #[cfg(feature = "tls-connections")] + tokio::select! { + Ok(value) = self.listener.accept() => accept_plain_branch(self, value, update_tx.clone()).await, + Ok(value) = self.tls.listener.accept() => accept_tls_branch(self, value, update_tx.clone()).await, + client_message = update_rx.recv(), if !self.clients.is_empty() => client_message_branch(self, client_message).await } } } async fn create_client( &mut self, - stream: TcpStream, + stream: ClientStream, addr: SocketAddr, ) -> Option { let entry = self.clients.vacant_entry(); @@ -263,6 +377,10 @@ pub struct NetworkLayerBuilder { listener: Option, + #[cfg(feature = "tls-connections")] + tls_listener: Option, + #[cfg(feature = "tls-connections")] + tls_acceptor: Option, clients_capacity: usize, rooms_capacity: usize, } @@ -273,6 +391,10 @@ clients_capacity: 1024, rooms_capacity: 512, listener: None, + #[cfg(feature = "tls-connections")] + tls_listener: None, + #[cfg(feature = "tls-connections")] + tls_acceptor: None, } } } @@ -285,6 +407,22 @@ } } + #[cfg(feature = "tls-connections")] + pub fn with_tls_acceptor(self, listener: TlsAcceptor) -> Self { + Self { + tls_acceptor: Option::from(listener), + ..self + } + } + + #[cfg(feature = "tls-connections")] + pub fn with_tls_listener(self, listener: TlsAcceptor) -> Self { + Self { + tls_acceptor: Option::from(listener), + ..self + } + } + pub fn build(self) -> NetworkLayer { let server_state = ServerState::new(self.clients_capacity, self.rooms_capacity); @@ -292,6 +430,11 @@ NetworkLayer { listener: self.listener.expect("No listener provided"), + #[cfg(feature = "tls-connections")] + tls: TlsListener { + listener: self.tls_listener.expect("No TLS listener provided"), + acceptor: self.tls_acceptor.expect("No TLS acceptor provided"), + }, server_state, clients, } diff -r 772a43d88e6b -r 2146cb7be36f rust/hwphysics/Cargo.toml --- a/rust/hwphysics/Cargo.toml Fri Mar 10 11:42:25 2023 +0100 +++ b/rust/hwphysics/Cargo.toml Tue Aug 22 08:35:46 2023 +0200 @@ -8,3 +8,10 @@ fpnum = { path = "../fpnum" } integral-geometry = { path = "../integral-geometry" } land2d = { path = "../land2d" } + +[dev-dependencies] +criterion = "0.4.0" + +[[bench]] +name = "ecs_bench" +harness = false \ No newline at end of file diff -r 772a43d88e6b -r 2146cb7be36f rust/hwphysics/benches/ecs_bench.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hwphysics/benches/ecs_bench.rs Tue Aug 22 08:35:46 2023 +0200 @@ -0,0 +1,119 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use hwphysics::{common::GearId, data::GearDataManager}; + +#[derive(Clone, Copy, Default)] +struct P { + position: u64, +} + +#[derive(Clone, Copy, Default)] +struct V { + velocity: u64, +} + +#[derive(Clone, Copy, Default)] +struct Pv { + position: u64, + velocity: u64, +} + +const SIZE: usize = 4 * 1024; + +pub fn array_run(c: &mut Criterion) { + let mut items = [Pv::default(); SIZE]; + + c.bench_function("array run", |b| { + b.iter(|| { + for item in &mut items { + item.velocity += black_box(item.position); + } + }) + }); +} + +pub fn component_run(c: &mut Criterion) { + let mut manager = GearDataManager::new(); + + manager.register::

(); + manager.register::(); + + for i in 1..=SIZE { + let gear_id = GearId::new(i as u16).unwrap(); + manager.add(gear_id, &P::default()); + manager.add(gear_id, &V::default()); + } + + c.bench_function("component run", |b| { + b.iter(|| { + manager + .iter() + .run(|(p, v): (&mut P, &mut V)| v.velocity += black_box(p.position)); + }) + }); +} + +pub fn component_multirun(c: &mut Criterion) { + for n in (16..=64).step_by(16) { + let mut manager = GearDataManager::new(); + + manager.register::

(); + manager.register::(); + + for i in 1..=(SIZE / n) { + let gear_id = GearId::new(i as u16).unwrap(); + manager.add(gear_id, &P::default()); + manager.add(gear_id, &V::default()); + } + + c.bench_function(&format!("component run {}", n), |b| { + b.iter(|| { + for i in 0..n { + manager + .iter() + .run(|(p, v): (&mut P, &mut V)| v.velocity += black_box(p.position)); + } + }) + }); + } +} + +pub fn component_add_remove(c: &mut Criterion) { + let mut manager = GearDataManager::new(); + let mut gears1 = vec![]; + let mut gears2 = vec![]; + + manager.register::

(); + manager.register::(); + + for i in 1..=SIZE { + let gear_id = GearId::new(i as u16).unwrap(); + manager.add(gear_id, &P::default()); + if i % 2 == 0 { + manager.add(gear_id, &V::default()); + gears1.push(gear_id); + } else { + gears2.push(gear_id); + } + } + + c.bench_function("component add/remove", |b| { + b.iter(|| { + for id in &gears2 { + manager.add(*id, &V::default()); + } + for id in &gears1 { + manager.remove::(*id); + } + std::mem::swap(&mut gears1, &mut gears2); + }) + }); +} + +criterion_group!( + benches, + array_run, + component_run, + component_multirun, + component_add_remove +); +criterion_main!(benches); diff -r 772a43d88e6b -r 2146cb7be36f rust/hwphysics/src/data.rs --- a/rust/hwphysics/src/data.rs Fri Mar 10 11:42:25 2023 +0100 +++ b/rust/hwphysics/src/data.rs Tue Aug 22 08:35:46 2023 +0200 @@ -9,17 +9,28 @@ slice, }; +const MAX_TYPES: usize = 8; + pub trait TypeTuple: Sized { - fn get_types(types: &mut Vec); + fn get_types(_types: &mut [TypeId; MAX_TYPES]) -> usize; } impl TypeTuple for () { - fn get_types(_types: &mut Vec) {} + fn get_types(_types: &mut [TypeId; MAX_TYPES]) -> usize { + 0 + } } impl TypeTuple for &T { - fn get_types(types: &mut Vec) { - types.push(TypeId::of::()); + fn get_types(types: &mut [TypeId; MAX_TYPES]) -> usize { + if MAX_TYPES > 0 { + unsafe { + *types.get_unchecked_mut(0) = TypeId::of::(); + } + 1 + } else { + 0 + } } } @@ -30,13 +41,22 @@ macro_rules! type_tuple_impl { ($($n: literal: $t: ident),+) => { impl<$($t: 'static),+> TypeTuple for ($(&$t),+,) { - fn get_types(types: &mut Vec) { - $(types.push(TypeId::of::<$t>()));+ + fn get_types(types: &mut [TypeId; MAX_TYPES]) -> usize { + let mut count = 0; + $({ + if MAX_TYPES > $n { + unsafe { + *types.get_unchecked_mut($n) = TypeId::of::<$t>(); + } + count = $n + 1; + } + });+ + count } } impl<$($t: 'static),+> TypeIter for ($(&$t),+,) { - unsafe fn iter(slices: &[*mut u8], count: usize, mut f: F) { + unsafe fn iter(slices: &[*mut u8], count: usize, mut f: FI) { for i in 0..count { f(*(*slices.get_unchecked(0) as *const GearId).add(i), ($(&*(*slices.get_unchecked($n + 1) as *mut $t).add(i)),+,)); @@ -45,13 +65,22 @@ } impl<$($t: 'static),+> TypeTuple for ($(&mut $t),+,) { - fn get_types(types: &mut Vec) { - $(types.push(TypeId::of::<$t>()));+ + fn get_types(types: &mut [TypeId; MAX_TYPES]) -> usize { + let mut count = 0; + $({ + if MAX_TYPES > $n { + unsafe { + *types.get_unchecked_mut($n) = TypeId::of::<$t>(); + } + count = $n + 1; + } + });+ + count } } impl<$($t: 'static),+> TypeIter for ($(&mut $t),+,) { - unsafe fn iter(slices: &[*mut u8], count: usize, mut f: F) { + unsafe fn iter(slices: &[*mut u8], count: usize, mut f: FI) { for i in 0..count { f(*(*slices.get_unchecked(0) as *const GearId).add(i), ($(&mut *(*slices.get_unchecked($n + 1) as *mut $t).add(i)),+,)); @@ -66,6 +95,9 @@ type_tuple_impl!(0: A, 1: B, 2: C); type_tuple_impl!(0: A, 1: B, 2: C, 3: D); type_tuple_impl!(0: A, 1: B, 2: C, 3: D, 4: E); +type_tuple_impl!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F); +type_tuple_impl!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G); +type_tuple_impl!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H); const BLOCK_SIZE: usize = 32768; @@ -140,14 +172,17 @@ .add(size_of::() * max_elements as usize) }; - for i in 0..element_sizes.len() { - if mask & (1 << i as u64) != 0 { - unsafe { - address = address.add(address.align_offset(element_alignments[i] as usize)); - blocks[i] = Some(NonNull::new_unchecked(address)); - address = address.add(element_sizes[i] as usize * max_elements as usize) - }; - } + let mut mask_bits = mask; + while mask_bits != 0 { + let i = mask_bits.trailing_zeros() as usize; + + unsafe { + address = address.add(address.align_offset(element_alignments[i] as usize)); + blocks[i] = Some(NonNull::new_unchecked(address)); + address = address.add(element_sizes[i] as usize * max_elements as usize) + }; + + mask_bits &= mask_bits - 1; } Self { @@ -159,6 +194,7 @@ } } + #[inline] fn gear_ids(&self) -> &[GearId] { unsafe { slice::from_raw_parts( @@ -168,6 +204,7 @@ } } + #[inline] fn gear_ids_mut(&mut self) -> &mut [GearId] { unsafe { slice::from_raw_parts_mut( @@ -177,6 +214,7 @@ } } + #[inline] fn is_full(&self) -> bool { self.elements_count == self.max_elements } @@ -218,6 +256,11 @@ } #[inline] + fn without_type(&self, type_bit: u64) -> Self { + Self::new(self.type_mask & !type_bit, self.tag_mask) + } + + #[inline] fn with_tag(&self, tag_bit: u64) -> Self { Self::new(self.type_mask, self.tag_mask | tag_bit) } @@ -267,7 +310,7 @@ debug_assert!(src_block_index != dest_block_index); let src_mask = self.block_masks[src_block_index as usize]; let dest_mask = self.block_masks[dest_block_index as usize]; - debug_assert!(src_mask.type_mask & dest_mask.type_mask == src_mask.type_mask); + debug_assert!(src_mask.type_mask & dest_mask.type_mask != 0); let src_block = &self.blocks[src_block_index as usize]; let dest_block = &self.blocks[dest_block_index as usize]; @@ -275,32 +318,40 @@ debug_assert!(!dest_block.is_full()); let dest_index = dest_block.elements_count; - for i in 0..self.types.len() { - if src_mask.type_mask & (1 << i as u64) != 0 { - let size = self.element_sizes[i]; - let src_ptr = src_block.component_blocks[i].unwrap().as_ptr(); - let dest_ptr = dest_block.component_blocks[i].unwrap().as_ptr(); + + let mut type_mask = src_mask.type_mask; + while type_mask != 0 { + let i = type_mask.trailing_zeros() as usize; + + let size = self.element_sizes[i]; + let src_ptr = src_block.component_blocks[i].unwrap().as_ptr(); + if let Some(dest_ptr) = dest_block.component_blocks[i] { + let dest_ptr = dest_ptr.as_ptr(); unsafe { copy_nonoverlapping( src_ptr.add((src_index * size) as usize), dest_ptr.add((dest_index * size) as usize), size as usize, ); - if src_index < src_block.elements_count - 1 { - copy_nonoverlapping( - src_ptr.add((size * (src_block.elements_count - 1)) as usize), - src_ptr.add((size * src_index) as usize), - size as usize, - ); - } } } + unsafe { + if src_index < src_block.elements_count - 1 { + copy_nonoverlapping( + src_ptr.add((size * (src_block.elements_count - 1)) as usize), + src_ptr.add((size * src_index) as usize), + size as usize, + ); + } + } + + type_mask &= type_mask - 1; } let src_block = &mut self.blocks[src_block_index as usize]; let gear_id = src_block.gear_ids()[src_index as usize]; - if src_index < src_block.elements_count - 1 { + if src_index + 1 < src_block.elements_count { let relocated_index = src_block.elements_count as usize - 1; let gear_ids = src_block.gear_ids_mut(); let relocated_id = gear_ids[relocated_index]; @@ -460,16 +511,24 @@ pub fn remove(&mut self, gear_id: GearId) { if let Some(type_index) = self.get_type_index::() { + let type_bit = 1 << type_index as u64; let entry = self.lookup[gear_id.get() as usize - 1]; + if let Some(index) = entry.index { - let mut dest_mask = self.block_masks[entry.block_index as usize]; - dest_mask.type_mask &= !(1 << type_index as u64); + let mask = self.block_masks[entry.block_index as usize]; + let new_mask = mask.without_type(type_bit); - if dest_mask.type_mask == 0 { - self.remove_from_block(entry.block_index, index.get() - 1); - } else { - let dest_block_index = self.ensure_block(dest_mask); - self.move_between_blocks(entry.block_index, index.get() - 1, dest_block_index); + if new_mask != mask { + if new_mask.type_mask == 0 { + self.remove_from_block(entry.block_index, index.get() - 1); + } else { + let dest_block_index = self.ensure_block(new_mask); + self.move_between_blocks( + entry.block_index, + index.get() - 1, + dest_block_index, + ); + } } } } else { @@ -511,7 +570,7 @@ type_indices: &[i8], mut f: F, ) { - let mut slices = vec![null_mut(); type_indices.len() + 1]; + let mut slices = [null_mut(); MAX_TYPES + 1]; for (block_index, mask) in self.block_masks.iter().enumerate() { if mask.type_mask & type_selector == type_selector @@ -527,7 +586,11 @@ } unsafe { - T::iter(&slices[..], block.elements_count as usize, |id, x| f(id, x)); + T::iter( + &slices[0..=type_indices.len()], + block.elements_count as usize, + |id, x| f(id, x), + ); } } } @@ -547,12 +610,12 @@ } pub fn iter(&mut self) -> DataIterator { - let mut arg_types = Vec::with_capacity(64); - T::get_types(&mut arg_types); - let mut type_indices = vec![-1i8; arg_types.len()]; + let mut arg_types: [TypeId; MAX_TYPES] = unsafe { MaybeUninit::uninit().assume_init() }; + let types_count = T::get_types(&mut arg_types); + let mut type_indices = [-1; MAX_TYPES]; let mut selector = 0u64; - for (arg_index, type_id) in arg_types.iter().enumerate() { + for (arg_index, type_id) in arg_types[0..types_count].iter().enumerate() { match self.types.iter().position(|t| t == type_id) { Some(i) if selector & (1 << i as u64) != 0 => panic!("Duplicate type"), Some(i) => { @@ -569,7 +632,7 @@ pub struct DataIterator<'a, T> { data: &'a mut GearDataManager, types: u64, - type_indices: Vec, + type_indices: [i8; MAX_TYPES], tags: u64, phantom_types: PhantomData, } @@ -578,7 +641,7 @@ fn new( data: &'a mut GearDataManager, types: u64, - type_indices: Vec, + type_indices: [i8; MAX_TYPES], ) -> DataIterator<'a, T> { Self { data, @@ -590,12 +653,12 @@ } pub fn with_tags(self) -> Self { - let mut tag_types = Vec::with_capacity(64); - U::get_types(&mut tag_types); + let mut tag_types: [TypeId; MAX_TYPES] = unsafe { MaybeUninit::uninit().assume_init() }; + let tags_count = U::get_types(&mut tag_types); let mut tags = 0; for (i, tag) in self.data.tags.iter().enumerate() { - if tag_types.contains(tag) { + if tag_types[0..tags_count].contains(tag) { tags |= 1 << i as u64; } } @@ -609,8 +672,13 @@ #[inline] pub fn run_id(&mut self, f: F) { + let types_count = self + .type_indices + .iter() + .position(|i| *i == -1) + .unwrap_or(self.type_indices.len()); self.data - .run_impl(self.types, self.tags, &self.type_indices, f); + .run_impl(self.types, self.tags, &self.type_indices[0..types_count], f); } } @@ -619,7 +687,12 @@ use super::{super::common::GearId, GearDataManager}; #[derive(Clone)] - struct Datum { + struct DatumA { + value: u32, + } + + #[derive(Clone)] + struct DatumB { value: u32, } @@ -629,15 +702,15 @@ #[test] fn direct_access() { let mut manager = GearDataManager::new(); - manager.register::(); + manager.register::(); for i in 1..=5 { - manager.add(GearId::new(i as u16).unwrap(), &Datum { value: i * i }); + manager.add(GearId::new(i as u16).unwrap(), &DatumA { value: i * i }); } for i in 1..=5 { assert_eq!( manager - .get::(GearId::new(i as u16).unwrap()) + .get::(GearId::new(i as u16).unwrap()) .unwrap() .value, i * i @@ -648,46 +721,72 @@ #[test] fn single_component_iteration() { let mut manager = GearDataManager::new(); - manager.register::(); + manager.register::(); + for i in 1..=5 { - manager.add(GearId::new(i as u16).unwrap(), &Datum { value: i }); + manager.add(GearId::new(i as u16).unwrap(), &DatumA { value: i }); } let mut sum = 0; - manager.iter().run(|(d,): (&Datum,)| sum += d.value); + manager.iter().run(|(d,): (&DatumA,)| sum += d.value); assert_eq!(sum, 15); - manager.iter().run(|(d,): (&mut Datum,)| d.value += 1); - manager.iter().run(|(d,): (&Datum,)| sum += d.value); + manager.iter().run(|(d,): (&mut DatumA,)| d.value += 1); + manager.iter().run(|(d,): (&DatumA,)| sum += d.value); assert_eq!(sum, 35); } #[test] fn tagged_component_iteration() { let mut manager = GearDataManager::new(); - manager.register::(); + manager.register::(); manager.register::(); - for i in 1..=10 { - let gear_id = GearId::new(i as u16).unwrap(); - manager.add(gear_id, &Datum { value: i }); - } for i in 1..=10 { let gear_id = GearId::new(i as u16).unwrap(); - if i & 1 == 0 { - manager.add_tag::(gear_id); - } + manager.add(gear_id, &DatumA { value: i }); + } + + for i in (2..=10).step_by(2) { + let gear_id = GearId::new(i as u16).unwrap(); + manager.add_tag::(gear_id); } let mut sum = 0; - manager.iter().run(|(d,): (&Datum,)| sum += d.value); + manager.iter().run(|(d,): (&DatumA,)| sum += d.value); assert_eq!(sum, 55); let mut tag_sum = 0; manager .iter() .with_tags::<&Tag>() - .run(|(d,): (&Datum,)| tag_sum += d.value); + .run(|(d,): (&DatumA,)| tag_sum += d.value); assert_eq!(tag_sum, 30); } + + #[test] + fn removal() { + let mut manager = GearDataManager::new(); + manager.register::(); + manager.register::(); + + for i in 1..=10 { + let gear_id = GearId::new(i as u16).unwrap(); + manager.add(gear_id, &DatumA { value: i }); + manager.add(gear_id, &DatumB { value: i }); + } + + for i in (1..=10).step_by(2) { + let gear_id = GearId::new(i as u16).unwrap(); + manager.remove::(gear_id); + } + + let mut sum_a = 0; + manager.iter().run(|(d,): (&DatumA,)| sum_a += d.value); + assert_eq!(sum_a, 30); + + let mut sum_b = 0; + manager.iter().run(|(d,): (&DatumB,)| sum_b += d.value); + assert_eq!(sum_b, 55); + } } diff -r 772a43d88e6b -r 2146cb7be36f rust/hwphysics/src/lib.rs --- a/rust/hwphysics/src/lib.rs Fri Mar 10 11:42:25 2023 +0100 +++ b/rust/hwphysics/src/lib.rs Tue Aug 22 08:35:46 2023 +0200 @@ -1,6 +1,6 @@ pub mod collision; pub mod common; -mod data; +pub mod data; mod grid; pub mod physics; diff -r 772a43d88e6b -r 2146cb7be36f rust/mapgen/src/theme.rs --- a/rust/mapgen/src/theme.rs Fri Mar 10 11:42:25 2023 +0100 +++ b/rust/mapgen/src/theme.rs Tue Aug 22 08:35:46 2023 +0200 @@ -1,3 +1,4 @@ +use integral_geometry::{Point, Rect}; use png::{ColorType, Decoder, DecodingError}; use std::{ fs::{read_dir, File}, @@ -109,9 +110,70 @@ } } +#[derive(Default)] +struct Color(u8, u8, u8, u8); + +pub struct LandObjectOverlay { + texture: ThemeSprite, + offset: Point, +} + +pub struct LandObject { + texture: ThemeSprite, + inland_rects: Vec, + outland_rects: Vec, + anchors: Vec, + overlays: Vec, +} + +pub struct LandSpray { + texture: ThemeSprite, + count: u16, +} + +#[derive(Default)] +pub struct ThemeColors { + border: Color, +} + +pub struct Flakes { + texture: ThemeSprite, + frames_count: u16, + frame_ticks: u16, + velocity: u16, + fall_speed: u16, +} + +#[derive(Default)] +pub struct Water { + top_color: Color, + bottom_color: Color, + opacity: u8, +} + +#[derive(Default)] +pub struct ThemeParts { + water: Water, + flakes: Option, + music: String, + sky: Color, + tint: Color, +} + +#[derive(Default)] pub struct Theme { + border_color: Color, + clouds_count: u16, + flatten_flakes: bool, land_texture: Option, border_texture: Option, + land_objects: Vec, + spays: Vec, + use_ice: bool, + use_snow: bool, + music: String, + normal_parts: ThemeParts, + sd_parts: ThemeParts, } impl Theme { @@ -145,10 +207,7 @@ impl Theme { pub fn new() -> Self { - Theme { - land_texture: None, - border_texture: None, - } + Default::default() } pub fn load(path: &Path) -> Result { diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Graphics/AmmoMenu/Ammos_ExtraDamage_comma.png Binary file share/hedgewars/Data/Graphics/AmmoMenu/Ammos_ExtraDamage_comma.png has changed diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Graphics/AmmoMenu/Ammos_bw_ExtraDamage_comma.png Binary file share/hedgewars/Data/Graphics/AmmoMenu/Ammos_bw_ExtraDamage_comma.png has changed diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Locale/es.txt --- a/share/hedgewars/Data/Locale/es.txt Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Locale/es.txt Tue Aug 22 08:35:46 2023 +0200 @@ -1,528 +1,538 @@ -; Spanish locale -; Revision 4632 - -00:00=Granada -00:01=Granada de frag. -00:02=Bazuca -00:03=Abejorro -00:04=Escopeta -00:05=Taladro -00:06=Pasar -00:07=Cuerda -00:08=Mina -00:09=Desert Eagle -00:10=Dinamita -00:11=Bate de béisbol -00:12=Shoryuken -00:13=seg. -00:14=Paracaídas -00:15=Bombardeo aéreo -00:16=Minado aéreo -00:17=Soplete -00:18=Construcción -00:19=Teletransporte -00:20=Cambiar erizo -00:21=Mortero -00:22=Látigo -00:23=Kamikaze -00:24=Tarta -00:25=Seducción -00:26=Sandía bomba -00:27=Granada infernal -00:28=Misil perforador -00:29=Lanzapelotas -00:30=Napalm -00:31=Avión teledirigido -00:32=Baja gravedad -00:33=Daño extra -00:34=Invulnerabilidad -00:35=Tiempo extra -00:36=Mira láser -00:37=Vampirismo -00:38=Rifle francotirador -00:39=Platillo volante -00:40=Cóctel molotov -00:41=Birdy -00:42=Dispositivo portátil de portales -00:43=Piano -00:44=Limbuger añejo -00:45=Rifle sinusoidal (beta) -00:46=Lanzallamas -00:47=Bomba lapa -00:48=Mazo -00:49=Resurrección -00:50=Bombardeo perforador aéreo -00:51=Bola de barro -00:52=No hay arma seleccionada -00:53=Cabina del tiempo -00:54=Pistola de barro - -; 01:00=Loading … -01:01=Empate -01:02=¡%1 venció! -01:03=Volumen %1% -01:04=Pausa -01:05=¿Seguro que quieres salir (%1 / %2)? -01:06=¡Muerte súbita! -01:07=%1 restante -01:08=Combustible: %1% -01:09=Sincronizando... -01:10=Usar esta herramienta no hará que acabe tu turno. -01:11=Esta herramienta o arma todavía no está disponible. -01:12=¡Última ronda antes de la muerte súbita! -01:13=¡%1 rondas hasta la muerte súbita! -01:14=¡Prepárate, %1! -01:15=mínimo -01:16=bajo -01:17=normal -01:18=alto -01:19=extremo -01:20=Nivel de elasticidad: %1 - -; Eventos -; El erizo (%1) ha muerto -02:00=¡%1 ha estirado la pata! -02:00=¡%1 ha visto la luz! -02:00=¡%1 no lo vio venir! -02:00=¡%1 se despide! -02:00=¡%1 ahora está en un lugar mejor! -02:00=¡%1 pasea por verdes praderas! -02:00=¡%1 acaba de conocer a su Creador! -02:00=¡%1 no pudo aguantar más! -02:00=¡%1 ha cumplido con su deber! -02:00=¡%1 hizo el sacrificio supremo! -02:00=¡%1 deja atrás este mundo mortal! -02:00=¡%1 ha expirado! -02:00=¡%1 será recordado con cariño! -02:00=¡%1 ha tenido un aneurisma! -02:00=%1 deja atrás una mujer y tres niños -02:00=%1 ha disparado su última bazuca -02:00=%1 ha lanzado su última granada -02:00=%1 ha cocinado su última tarta -02:00=%1 se ha columpiado de su última cuerda -02:00=%1 ha solicitado su último bombardeo aéreo -02:00=%1 ha disparado su última escopeta -02:00=%1 ha lanzado su último melón -02:00=%1 ha apuntado su última pistola -02:00=%1 pensó que aguantaría una más -02:00=%1 habría agradecido un botiquín más -02:00=%1 se ha ido a jugar a un juego mejor -02:00=%1 se ha picado -02:00=%1 falló -02:00=Pobrecito %1... -02:00=%1 prefiere Warmux -02:00=%1 intentó parar las balas con su cara -02:00=%1 es un héroe entre los hom... digo.. erizos -02:00=%1 encontró el camino al Valhala -02:00=%1 has left the building -02:00=%1 siguió la misma suerte que los dinosaurios -02:00=%1 acerca los erizos un poco más a la extinción -02:00=%1, haces que se me humedezcan los ojos -02:00=%1 es un ex-erizo -02:00=%1 se fue a criar malvas -02:00=%1 ha dejado de ser -02:00=Despedíos de %1 -02:00=No hay esperanza para %1 -02:00=%1 recorrió la última milla -02:00=%1 sufrió un Error Fatal -02:00=%1 está frío como una piedra -02:00=%1 ha expirado -02:00=%1 se une al coro celestial -02:00=¡Cuídate, %1, ojalá nos hubiéramos llegado a conocer mejor! -02:00=%1 tenía intolerancia a las balas -02:00=%1 habría necesitado una vida extra -02:00=¿Hay algún médico en la sala? -02:00=¡Zas! ¡En toda la boca! - -; El erizo (%1) se ha ahogado -02:01=¡%1 hace el submarino! -02:01=¡%1 imita al Titanic! -02:01=¡%1 nada como una piedra! -02:01=¡%1 flota como un ladrillo! -02:01=¡%1 flota como el plomo! -02:01=%1 investiga a fondo -02:01=%1 hizo "glu, glu, glu" -02:01=%1 hizo "splash" -02:01=%1 olvidó sus brazaletes -02:01=A %1 le habrían venido realmente bien aquellas clases de natación -02:01=%1 olvidó su tabla de surf -02:01=%1 tiene los dedos arrugados -02:01=%1 está chorreando -02:01=%1 olvidó su salvavidas -02:01=%1 está durmiendo con los peces -02:01=%1 piensa que la simulación de fluidos de este juego apesta -02:01=%1 tenía sed, MUCHA sed -02:01=El océano reclamó a %1 -02:01=%1 está perdido en el mar -02:01=%1 debería haber traído sus gafas de bucear -02:01=%1 ha sido enterrado en el mar -02:01=%1 tuvo una sensación de pesadez -02:01=%1 está practicando su zambullida -02:01=%1 se fue a buscar el Titanic -02:01=%1 no es como Jesús -02:01=%1 está buscando a Nemo -02:01=Te asombraría saber cuántos erizos hay ahí abajo -02:01=%1 hizo que el nivel del mar subiera un pelín -02:01=%1 no se alistó a la marina -02:01=%1 hace su imitación del pez muerto -02:01=Al menos no te tiraron por el váter, %1 -02:01=Sonic no podía nadar y tú tampoco, %1 -02:01=%1 prefiere jugar a Ecco the dolphin -02:01=%1 ha ido a visitar Aquaria -02:01=%1 ha encontrado la ciudad perdida de la Atlántida -02:01=Necesitas practicar más tu estilo perrito, %1 -02:01=Necesitas practicar más tu brazada, %1 -02:01=Necesitas practicar más tu estilo mariposa, %1 -02:01=%1 debería haber traído sus esquís acuáticos -02:01=A %1 no le gustan los deportes acuáticos -02:01=%1 estará haciendo burbujas para siempre -02:01=%1 no pensó que fuera tan profundo -02:01=%1 cree que el agua salada es buena para la piel -02:01=El agua salada cura las heridas, %1 -02:01=%1 paseó por la tabla -02:01=%1 se bañó -02:01=%1 se remojó -02:01=%1 está mojado, mojado, mojado -02:01=No olvides el jabón, %1 -02:01=¡No salpiques, %1! -02:01=¿Estaba fría el agua? - -; El combate empieza -02:02=¡Luchad! -02:02=¡Armado y listo! -02:02=Vamos a montar una buena fiesta -02:02=El último erizo en pie gana -02:02=¡Vamos! -02:02=¡Let's rock! -02:02=¡Al lío! -02:02=En el comienzo... -02:02=Este es el principio de algo grande -02:02=Bienvenidos a Hedgewars -02:02=Bienvenido al frente, soldado -02:02=¡Machaca al enemigo! -02:02=Que gane el mejor erizo -02:02=Victoria o muerte -02:02=Hasta la victoria, siempre -02:02=Perder no es una opción -02:02=¡Soltad los erizos de la guerra! -02:02=Hedgewars, presentado por Hedgewars.org -02:02=Tienes suerte si no juegas contra Tiyuri -02:02=Tienes suerte si no juegas contra unC0Rr -02:02=Tienes suerte si no juegas contra Nemo -02:02=Tienes suerte si no juegas contra Smaxx -02:02=Tienes suerte si no juegas contra Jessor -02:02=¡Da lo mejor! -02:02=¡El que pierda, paga! -02:02=Que empiece la batalla del milenio -02:02=Que empiece la batalla del siglo -02:02=Que empiece la batalla de la década -02:02=Que empiece la batalla del año -02:02=Que empiece la batalla del mes -02:02=Que empiece la batalla de la semana -02:02=Que empiece la batalla del día -02:02=Que empiece la batalla de la hora -02:02=¡Hazlo lo mejor que puedas! -02:02=¡Destruye al enemigo! -02:02=Buena suerte -02:02=Diviértete -02:02=Lucha limpiamente -02:02=Lucha suciamente -02:02=Lucha con honore -02:02=Si haces trampas, procura que no te pillen -02:02=Nunca abandones -02:02=Nunca te rindas -02:02=¡Que empiece la marcha! -02:02=¡Espero que estés listo para el meneo! -02:02=¡Vamos, vamos, vamos! -02:02=Tropas, ¡avanzad! -02:02=¡Dadles caña! -02:02=¡No temáis! - -; Round ends and team/clan (%1) wins -02:03=¡%1 venció! - -; Round ends in a draw -02:04=Empate - -; Botiquín -02:05=¡Ayuda en camino! -02:05=¡Médico! -02:05=¡Primeros auxilios desde el cielo! -02:05=Un buen lote de medicamentos para ti -02:05=¡Buena salud... en forma de caja! -02:05=La llamada del doctor -02:05=¡Tiritas frescas! -02:05=Vendas limpias -02:05=Esto te hará sentir mejor -02:05=¡Una poción para ti! Ups, juego equivocado -02:05=¡Un paquete para recoger! -02:05=Cógelo -02:05=Una barrita saludable -02:05=Una cura para el dolor -02:05=Posología: ¡tantos como puedas conseguir! -02:05=Envío urgente -02:05=¡Víveres! - -; Caja de armamento -02:06=¡Más armas! -02:06=¡Refuerzos! -02:06=¡Armado y listo! -02:06=Me pregunto qué arma habrá ahí dentro... -02:06=¡Víveres! -02:06=¿Qué habrá dentro? -02:06=La navidad llega antes a Hedgewars -02:06=¡Un regalito! -02:06=¡Envío especial! -02:06=No sabes qué pesadilla ha sido atravesar la aduana con esto -02:06=Juguetes destructivos del Cielo -02:06=¡Cuidado! Volátil -02:06=¡Cuidado! Inflamable -02:06=Cógelo o reviéntalo, la elección es tuya -02:06=¡Mmmmm, armas! -02:06=Una caja de poder destructivo -02:06=¡Correo aéreo! -02:06=Contenga lo que contenga esa caja, seguro que no es pizza -02:06=¡Cógelo! -02:06=Envío de armas en camino -02:06=Refuerzos en camino -02:06=¡No dejes que el enemigo te lo quite! -02:06=¡Nuevos juguetitos! -02:06=¡Una caja misteriosa! - -; Caja de herramientas -02:07=¡La hora de la herramienta! -02:07=Esto podría ser útil... -02:07=¡Herramientas! -02:07=Usa esta caja -02:07=Cuidado los de abajo -02:07=¡Más herramientas! -02:07=¡Herramientas para ti! -02:07=¡Esto te vendrá bien! -02:07=Úsalo correctamente -02:07=Guau, esta caja es pesada -02:07=Podrías necesitarlo - -; El erizo %1 pasa su turno -02:08=%1 es un muermo... -02:08=%1 ni se molesta -02:08=%1 es un erizo perezoso -02:08=%1 tiene la mente en blanco -02:08=%1 abandona -02:08=El que quiera peces debe mojarse el culo, %1 -02:08=%1 abandona vergonzosamente el frente -02:08=%1 es muy muy vago -02:08=%1 necesita un poco más de motivación -02:08=%1 es un pacifista -02:08=%1 necesita su inhalador -02:08=%1 echa una cabezada -02:08=%1 se relaja -02:08=%1 se tumba a la bartola -02:08=Ommmmmm... -02:08=%1 no tiene confianza en sí mismo -02:08=%1 decide no hacer nada en absoluto -02:08=%1 deja que el enemigo se destruya a sí mismo -02:08=%1 debe ser un muermo en las fiestas -02:08=%1 se esconde -02:08=%1 ha dejado pasar esta oportunidad -02:08=%1 ha decidido que lo mejor que puede hacer es... nada -02:08=%1 es un cobardica -02:08=Co-Co-Cococó, %1 es un gallina -02:08=¡%1 es un cobarde! -02:08=%1 está esperando a la muerte súbita -02:08=%1 no se encuentra en forma -02:08=%1 está reconsiderando el sentido de su vida -02:08=%1 nunca tuvo mucha puntería, de todas formas -02:08=%1 nunca quiso alistarse en el ejército en realidad -02:08=No nos hagas perder el tiempo, %1 -02:08=Me has decepcionado, %1 -02:08=Vamos, %1, eres capaz de hacerlo mejor -02:08=La voluntad de %1 se quebró -02:08=Por lo visto %1 tiene mejores cosas que hacer -02:08=%1 está paralizado de terror -02:08=%1 se ha dormido - -; El erizo %1 se daña únicamente a sí mismo -02:09=¡%1 debería ir al campo de tiro a practicar! -02:09=%1 se odia a sí mismo -02:09=¡%1 estaba en el lado equivocado! -02:09=%1 es un poco emo -02:09=%1 tenía el arma del revés -02:09=%1 es un poco sádico -02:09=%1 es un masoquista -02:09=%1 no tiene instinto de supervivencia -02:09=%1 la pifió -02:09=%1 la fastidió -02:09=Ese fue un tiro pésimo, %1 -02:09=%1 es demasiado descuidado como para usar armas peligrosas -02:09=%1, deberías considerar un cambio de profesión -02:09=¡Peor! ¡Tiro! ¡Historia! -02:09=¡No, no, no, %1, debes disparar AL ENEMIGO! -02:09=%1 debería estar destruyendo enemigos -02:09=%1 se acerca un poco más al suicidio -02:09=%1 le echa una mano al enemigo -02:09=Eso fue una estupidez, %1 -02:09=%1 vive con la máxima "sin dolor no hay honor" -02:09=%1 está confuso -02:09=%1 se dispara a sí mismo en su confusión -02:09=¡%1 tiene un don para hacerse daño! -02:09=¡%1 es un patoso! -02:09=%1 es torpe -02:09=%1 le acaba de demostrar al enemigo de lo que es capaz -02:09=No se puede esperar que %1 sea perfecto todo el tiempo -02:09=No te preocupes, %1, nabie es ferpecto -02:09=¡Pues claro que %1 hizo eso a propósito! -02:09=No se lo diré a nadie si tú tampoco lo haces, %1 -02:09=¡Qué vergüenza! -02:09=Seguro que nadie te ha visto, %1 -02:09=%1 necesita revisar el manual -02:09=Las armas de %1 eran obviamente defectuosas - -; Home run (usando el bate de béisbol) -02:10=¡Home Run! -02:10=Es un pájaro, es un avión... -02:10=¡Eliminado! - -; El erizo (%1) abandona (el equipo ha salido de la partida) -02:11=¡%1 tiene que irse a mimir! -02:11=¡%1 tiene que irse a la cama! -02:11=Parece que %1 está demasiado ocupado para seguir jugando -02:11=¡Teletranspórtame, Scotty! -02:11=%1 tiene que irse - -; Categorías de armamento -03:00=Arma arrojadiza -03:01=Arma arrojadiza -03:02=Artillería -03:03=Misil -03:04=Arma de fuego (múltiples disparos) -03:05=Herramienta de excavación -03:06=Acción -03:07=Herramienta de transporte -03:08=Bomba de proximidad -03:09=Arma de fuego (disparo único) -03:10=¡BUM! -03:11=¡Bonk! -03:12=Artes marciales -03:13=SIN USAR -03:14=Herramienta de transporte -03:15=Ataque por aire -03:16=Ataque por aire -03:17=Herramienta de excavación -03:18=Herramienta -03:19=Herramienta de transporte -03:20=Acción -03:21=Arma balística -03:22=¡Llámame Indiana! -03:23=Artes marciales (en serio) -03:24=¡La tarta NO ES una mentira! -03:25=Disfraz -03:26=Arma arrojadiza jugosa -03:27=Arma arrojadiza fogosa -03:28=Artillería -03:29=Artillería -03:30=Ataque por aire -03:31=Bomba radiocontrolada -03:32=Efecto temporal -03:33=Efecto temporal -03:34=Efecto temporal -03:35=Efecto temporal -03:36=Efecto temporal -03:37=Efecto temporal -03:38=Arma de fuego (disparo único) -03:39=Herramienta de transporte -03:40=Bomba incendiaria -03:41=Amigo chillón -03:42=Creo que voy a tomar una nota... -03:43=E interpretando el Cascanueces tenemos a... -03:44=Consumir preferentemente antes de 1923 -03:45=¡El poder de la ciencia! -03:46=¡Caliente caliente caliente! -03:47=¡Pégalo en un buen sitio! -03:48=Pablo clavó un clavito -03:49=Hace exactamente lo que dice -03:50=Para los amantes de los topos -03:51=Me la encontré por el suelo -03:52=SIN USAR -03:53=Tipo 40 -03:54=Herramienta - -; Descripciones de armamento ( líneas delimitadas con | ) -04:00=Ataca a tus enemigos usando una sencilla granada.|Explotará una vez el temporizador llegue a cero.|1-5: ajustar temporizador.|Atacar: mantener presionado para lanzar más lejos. -04:01=Ataca a tus enemigos usando una granada de fragmentación.|Se fragmentará en metralla explosiva|una vez el temporizador llegue a cero.|1-5: ajustar temporizador.|Atacar: mantener presionado para lanzar más lejos. -04:02=Ataca a tus enemigos usando un proyectil balístico.|¡Atención al viento, modificará su trayectoria!|Atacar: mantener presionado para lanzar más lejos. -04:03=Lanza un abejorro explosivo que buscará el objetivo marcado.|No dispares a máxima potencia para mejorar su precisión.|Ratón: seleccionar objetivo.|Atacar: mantener presionado para lanzar más lejos. -04:04=Ataca a tus enemigos usando una escopeta de dos cañones.|Las balas se dispersan, así que no necesitarás|un tiro directo para herir a tus oponentes.|Atacar: abrir fuego (dos tiros). -04:05=¡Entiérrate! Usa el martillo neumático para excavar|un pozo en el suelo y alcanzar otras áreas.|Atacar: empezar o terminar de cavar. -04:06=¿Aburrido? ¿Sin posibilidad de atacar? ¿Racionas tu munición?|¡No hay problema! ¡Adelante, pasa esta turno, gallina!|Atacar: pasa este turno sin hacer nada. -04:07=Cubre grandes distancias usando hábilmente la cuerda.|Gana inercia para empujar a otros erizos|o deja caer granadas u otras armas sobre ellos.|Atacar: lanza o suelta la cuerda.|Salto: deja caer el arma seleccionada. -04:08=Mantén alejados a tus enemigos desplegando minas|en pasadizos estrechos o justo bajo sus pies.|¡Asegúrate de alejarte rápidamente para no activarla tú mismo!|Atacar: deposita una mina ante ti. -04:09=¿No confías en tu puntería? Con la desert eagle|tienes 4 disparos para conseguir alcanzar a tu enemigo.|Atacar: abrir fuego (hasta 4 veces). -04:10=La fuerza bruta siempre es una opción. Coloca este clásico|explosivo cerca de tus enemigos y huye.|Atacar: deposita la dinamita ante ti. -04:11=¡Manda a tus enemigos lejos de ti de un buen batazo!|Acaba con ellos lanzándolos fuera del mapa o al agua.|¿O qué tal lanzarles algunas minas?|Atacar: batear cualquier cosa delante de ti. -04:12=Enfréntate cara a cara con tus enemigos|y libera el poder de tus puños sobre ellos.|Útil para lanzarlos fuera del mapa o al agua.|Atacar: ejecutar el puño de fuego. -04:13=SIN USAR -04:14=¿Te dan miedo las alturas? Nunca más con un buen paracaídas.|Se desplegará automáticamente cuando caigas suficientemente lejos.|Atacar: desplegar/replegar el paracaídas.|Cursores: controlar el descenso. -04:15=Haz llover bombas sobre tus enemigos solicitando un bombardeo aéreo.|Derecha/izquierda: determinar dirección del ataque.|Ratón: seleccionar objetivo. -04:16=Haz llover minas sobre tus enemigos solicitando un minado aéreo.|Derecha/izquierda: determinar dirección del ataque.|Ratón: seleccionar objetivo. -04:17=¿Buscas refugio? ¿Necesitas salir de una cueva?|Usa el soplete para cavar un túnel a través del terreno.|Atacar: encender/apagar el soplete. -04:18=¿Necesitas protección adicional o quieres atravesar algún abismo?|Coloca tantas vigas como quieras/puedas.|Derecha/izquierda: seleccionar tipo de viga.|Ratón: colocar viga. -04:19=Usado en el momento adecuado, el teletransporte puede ser|tu mayor aliado, ayudándote a escapar de situaciones mortales|o alcanzar víveres valiosos.|Ratón: seleccionar objetivo. -04:20=Te permite jugar este turno con otro de tus erizos.|Atacar: activar.|Tabulador: cambiar entre erizos una vez activada. -04:21=Lanza un proyectil que se fragmentará al impactar, enviando|una lluvia explosiva sobre tus enemigos.|Atacar: lanzar a máxima potencia. -04:22=¡Siéntete como Indiana Jones! El látigo es un arma muy útil|en ciertas situaciones, especialmente para deshacerte de|erizos enemigos enviándolos fuera del mapa o al agua.|Atacar: golpear cualquier cosa delante de ti. -04:23=Si no tienes nada que perder, esto puede serte útil.|Sacrifica a tu erizo lanzándolo como un cohete que despejará|cualquier cosa que encuentre en su camino, detonando al final.|Atacar: manda a tu erizo a la perdición. -04:24=¡Feliz cumpleaños! Esta tarta bípeda caminará|hasta tus enemigos para darles una fiesta sorpresa explosiva.|La tarta es capaz de atravesar casi cualquier tipo de terreno,|pero evita que se quede atascada.|Atacar: enviar la tarta de camino o hacerla detonar. -04:25=Utiliza este disfraz para seducir a tus enemigos,|haciéndoles perder la cabeza y saltar como locos hacia ti|(y de paso hacia el agua o una mina).|Atacar: disfrazarte y lanzar un beso a tus enemigos. -04:26=Lanza esta jugosa sandía a tus enemigos.|Una vez el temporizador llegue a cero se fragmentará|en rodajas deliciosamente explosivas sobre tus enemigos.|Atacar: mantener presionado para lanzar más lejos. -04:27=Haz que el fuego del averno chamusque a tus enemigos|usando esta granada infernal.|¡Aléjate todo lo que puedas de la explosión,|la onda expansiva y el fuego tienen gran alcance!|Atacar: mantener presionado para lanzar más lejos. -04:28=Una vez lanzado, este proyectil comenzará a cavar tan pronto toque tierra|y explotará al volver a salir a la superficie o al encontrar algún obstáculo,|como un erizo enemigo o una caja.|Atacar: mantener presionado para lanzar más lejos. -04:29=¡Puede parecer un juguete, pero no lo es!|El lanzapelotas dispara montones de pelotas multicolores|llenas de explosivos que rebotarán hasta tus enemigos.|Atacar: lanzar a máxima potencia.|Arriba/abajo: modificar ángulo de disparo. -04:30=Haz llover fuego sobre tus enemigos solicitando un ataque aéreo.|Con la destreza adecuada, este ataque puede erradicar grandes áreas de tierra.|Derecha/izquierda: determinar dirección del ataque.|Ratón: seleccionar objetivo. -04:31=El avión teledirigido es el arma ideal para recoger cajas|o atacar enemigos lejanos.|Cargado con 3 bombas, el avión explotará|si choca contra algo.|Atacar: lanzar el avión o dejar caer las bombas.|Cursores: controlar el avión. -04:32=¡Mucho mejor que cualquier dieta! Salta más alto y más lejos|o haz que tus enemigos vuelen incluso más lejos.|Atacar: activar. -04:33=A veces uno necesita una pequeña ayuda para acabar con sus enemigos.|Atacar: activar. -04:34=¡Na, na, na, no me tocas!|Atacar: activar. -04:35=A veces el reloj corre demasiado deprisa. Consigue un poco|de tiempo extra para finalizar tu ataque.|Atacar: activar. -04:36=Vaya, parece que tu puntería apesta. Por suerte para ti|la tecnología moderna está de tu lado.|Atacar: activar. -04:37=No le temas a la luz del día. Sólo durará un turno, pero|te permitirá absorber la fuerza vital de tus enemigos|cuando les ataques.|Atacar: activar. -04:38=El rifle de francotirador puede ser el arma más destructiva|de todo tu arsenal, pero es muy inefectiva en distancias cortas.|El daño infligido es proporcional a la distancia respecto del objetivo.|Atacar: abrir fuego (un disparo). -04:39=Vuela hasta otras partes del mapa usando un platillo volante.|Puede ser complicado de controlar, pero conseguirás llegar|a sitios que nunca hubieras imaginado accesibles.|Atacar: activar.|Cursores: acelerar en esa dirección (un golpe cada vez). -04:40=Alza un muro de fuego usando esta botella|llena de (en breve, ardiendo) líquido inflamable.|Atacar: mantener presionado para lanzar más lejos. -04:41=¡Demostrando que lo natural puede ser mejor|que lo artificial, Birdy puede no sólo|transportar tu erizo como el platillo volante,|sino también lanzar huevos envenenados a tus enemigos!|Atacar: activar/lanzar huevos.|Cursores: aletear en esa dirección. -04:42=El dispositivo portátil de portales es capaz de|transportar instantáneamente minas, armas o ¡incluso erizos!|Úsalo adecuadamente y tu campaña será un... |¡ÉXITO ALUCINANTE!|Atacar: disparar un portal.|Cambiar: alternar el color a disparar. -04:43=¡Haz un debut explosivo en el mundo del espectáculo!|Lanza un piano desde lo más alto del firmamento, pero ten cuidado...|¡alguien debe tocarlo, y eso puede costarte la vida!|Ratón: seleccionar objetivo.|F1-F9: tocar el piano. -04:44=¡No es simplemente queso, es un arma biológica!|No causará mucho daño al detonar, pero ten por seguro|que cualquiera que se acerque demasiado|a su oloroso rastro quedará gravemente intoxicado.|1-5: ajustar temporizador.|Atacar: mantener presionado para lanzar más lejos. -04:45=Al fin una utilidad para todas esas clases de física.|Dispara una devastadora onda sinusoidal que mandará|a tus enemigos al infierno matemático.|Ten cuidado, el retroceso de este arma es considerable.|Atacar: disparar. -04:46=Envuelve a tus enemigos en siseante fuego líquido.|¡Se derretirán de placer!|Atacar: activar.|Arriba/abajo: modificar trayectoria.|Izquierda/derecha: modificar potencia de fuego. -04:47=¡Dos bombas lapa, doble diversión!|Útiles para planear reacciones en cadena, atrincherarte...|¡o las dos cosas!.|Atacar: mantener presionado para lanzar más lejos (dos disparos). -04:48=¿Por qué la gente siempre la toma con los topos?|¡Golpear erizos es aún más divertido!|Un buen mazazo puede reducir en un tercio la|vida de cualquier erizo y enterrarlo completamente.|Atacar: activar. -04:49=¡Resucita a tus aliados!|Pero ten cuidado, también resucitarás a tus enemigos.|Atacar: mantener presionado para resucitar lentamente.|Arriba: acelerar resurrección. -04:50=¿Alguien está oculto bajo tierra?|¡Desentiérralos con un bombardeo perforador!|El temporizador controla la profundidad a alcanzar. -04:51=¿Qué hay más barato que el barro?|Un tiro gratis gracias a la bola de barro.|Hará que el enemigo salga volando|y escuece un poco si te entra en los ojos. -04:52=SIN USAR -04:53=Vive una trepidante aventura a través del|espacio y el tiempo mientras tus compañeros|siguen luchando en tu lugar.|Estate preparado para volver en cualquier momento,|o al llegar la Muerte súbita si te has quedado solo.|Aviso\: no funciona durante la Muerte súbita,|si estás solo o si eres el rey. -04:54=Esparce un chorro de pegajoso barro.|Construye puentes, entierra enemigos o cierra túneles.|¡Ten especial cuidado de no mancharte! - -; Game goal strings -05:00=Modos de juego -05:01=Las siguientes reglas están activas: -05:02=Posicionar el rey: elige un buen cobijo para tu rey -05:03=Baja gravedad: mira bien dónde pisas -05:04=Invulnerabilidad: todos los erizos tienen un campo de fuerza personal que los protege -05:05=Vampirismo: dañar a tus enemigos te curará a ti -05:06=Karma: compartirás parte del daño que inflijas -05:07=Rey: ¡no permitas que tu rey muera! -05:08=Posicionar erizos: los jugadores posicionan a mano su erizos por turnos antes de empezar a jugar -05:09=Artillería: afina tu puntería, los erizos no pueden moverse -05:10=Terreno indestructible: la mayoría de armas no pueden dañar el terreno de juego -05:11=Munición compartida: los equipos del mismo color comparten la munición -05:12=Minas: las minas detonarán a cabo de %1 segundo(s) -05:13=Minas: las minas detonarán al instante -05:14=Minas: las minas detonarán aleatoriamente al cabo de 0 - 3 segundos -05:15=Modificador al daño: las armas harán un %1% de su daño habitual -05:16=La salud de todos los erizos se restaura al final de cada turno -05:17=La computadora resucita al morir -05:18=Sin límite de ataques por turno -05:19=El arsenal se restaura al final de cada turno -05:20=Los erizos no comparten arsenal -05:21=Tag Team: los equipos del mismo clan se van turnando entre ellos.|Turno compartido: los equipos del mismo clan comparten la duración del turno. +; Spanish locale +; Revision 4632 + +00:00=Granada +00:01=Granada de frag. +00:02=Bazuca +00:03=Abejorro +00:04=Escopeta +00:05=Taladro +00:06=Pasar +00:07=Cuerda +00:08=Mina +00:09=Desert Eagle +00:10=Dinamita +00:11=Bate de béisbol +00:12=Shoryuken +00:13=seg. +00:14=Paracaídas +00:15=Bombardeo aéreo +00:16=Minado aéreo +00:17=Soplete +00:18=Construcción +00:19=Teletransporte +00:20=Cambiar erizo +00:21=Mortero +00:22=Látigo +00:23=Kamikaze +00:24=Tarta +00:25=Seducción +00:26=Sandía bomba +00:27=Granada infernal +00:28=Misil perforador +00:29=Lanzapelotas +00:30=Napalm +00:31=Avión teledirigido +00:32=Baja gravedad +00:33=Daño extra +00:34=Invulnerabilidad +00:35=Tiempo extra +00:36=Mira láser +00:37=Vampirismo +00:38=Rifle francotirador +00:39=Platillo volante +00:40=Cóctel molotov +00:41=Birdy +00:42=Dispositivo portátil de portales +00:43=Piano +00:44=Limbuger añejo +00:45=Rifle sinusoidal (beta) +00:46=Lanzallamas +00:47=Bomba lapa +00:48=Mazo +00:49=Resurrección +00:50=Bombardeo perforador aéreo +00:51=Bola de barro +00:52=No hay arma seleccionada +00:53=Cabina del tiempo +00:54=Pistola de barro + +; 01:00=Loading … +01:01=Empate +01:02=¡%1 venció! +01:03=Volumen %1% +01:04=Pausa +01:05=¿Seguro que quieres salir (%1 / %2)? +01:06=¡Muerte súbita! +01:07=%1 restante +01:08=Combustible: %1% +01:09=Sincronizando... +01:10=Usar esta herramienta no hará que acabe tu turno. +01:11=Esta herramienta o arma todavía no está disponible. +01:12=¡Última ronda antes de la muerte súbita! +01:13=¡%1 rondas hasta la muerte súbita! +01:14=¡Prepárate, %1! +01:15=mínimo +01:16=bajo +01:17=normal +01:18=alto +01:19=extremo +01:20=Nivel de elasticidad: %1 + +; Eventos +; El erizo (%1) ha muerto +02:00=¡%1 ha estirado la pata! +02:00=¡%1 ha visto la luz! +02:00=¡%1 no lo vio venir! +02:00=¡%1 se despide! +02:00=¡%1 ahora está en un lugar mejor! +02:00=¡%1 pasea por verdes praderas! +02:00=¡%1 acaba de conocer a su Creador! +02:00=¡%1 no pudo aguantar más! +02:00=¡%1 ha cumplido con su deber! +02:00=¡%1 hizo el sacrificio supremo! +02:00=¡%1 deja atrás este mundo mortal! +02:00=¡%1 ha expirado! +02:00=¡%1 será recordado con cariño! +02:00=¡%1 ha tenido un aneurisma! +02:00=%1 deja atrás una mujer y tres niños +02:00=%1 ha disparado su última bazuca +02:00=%1 ha lanzado su última granada +02:00=%1 ha cocinado su última tarta +02:00=%1 se ha columpiado de su última cuerda +02:00=%1 ha solicitado su último bombardeo aéreo +02:00=%1 ha disparado su última escopeta +02:00=%1 ha lanzado su último melón +02:00=%1 ha apuntado su última pistola +02:00=%1 pensó que aguantaría una más +02:00=%1 habría agradecido un botiquín más +02:00=%1 se ha ido a jugar a un juego mejor +02:00=%1 se ha picado +02:00=%1 falló +02:00=Pobrecito %1... +02:00=%1 prefiere Warmux +02:00=%1 intentó parar las balas con su cara +02:00=%1 es un héroe entre los hom... digo.. erizos +02:00=%1 encontró el camino al Valhala +02:00=%1 has left the building +02:00=%1 siguió la misma suerte que los dinosaurios +02:00=%1 acerca los erizos un poco más a la extinción +02:00=%1, haces que se me humedezcan los ojos +02:00=%1 es un ex-erizo +02:00=%1 se fue a criar malvas +02:00=%1 ha dejado de ser +02:00=Despedíos de %1 +02:00=No hay esperanza para %1 +02:00=%1 recorrió la última milla +02:00=%1 sufrió un Error Fatal +02:00=%1 está frío como una piedra +02:00=%1 ha expirado +02:00=%1 se une al coro celestial +02:00=¡Cuídate, %1, ojalá nos hubiéramos llegado a conocer mejor! +02:00=%1 tenía intolerancia a las balas +02:00=%1 habría necesitado una vida extra +02:00=¿Hay algún médico en la sala? +02:00=¡Zas! ¡En toda la boca! + +; El erizo (%1) se ha ahogado +02:01=¡%1 hace el submarino! +02:01=¡%1 imita al Titanic! +02:01=¡%1 nada como una piedra! +02:01=¡%1 flota como un ladrillo! +02:01=¡%1 flota como el plomo! +02:01=%1 investiga a fondo +02:01=%1 hizo "glu, glu, glu" +02:01=%1 hizo "splash" +02:01=%1 olvidó sus brazaletes +02:01=A %1 le habrían venido realmente bien aquellas clases de natación +02:01=%1 olvidó su tabla de surf +02:01=%1 tiene los dedos arrugados +02:01=%1 está chorreando +02:01=%1 olvidó su salvavidas +02:01=%1 está durmiendo con los peces +02:01=%1 piensa que la simulación de fluidos de este juego apesta +02:01=%1 tenía sed, MUCHA sed +02:01=El océano reclamó a %1 +02:01=%1 está perdido en el mar +02:01=%1 debería haber traído sus gafas de bucear +02:01=%1 ha sido enterrado en el mar +02:01=%1 tuvo una sensación de pesadez +02:01=%1 está practicando su zambullida +02:01=%1 se fue a buscar el Titanic +02:01=%1 no es como Jesús +02:01=%1 está buscando a Nemo +02:01=Te asombraría saber cuántos erizos hay ahí abajo +02:01=%1 hizo que el nivel del mar subiera un pelín +02:01=%1 no se alistó a la marina +02:01=%1 hace su imitación del pez muerto +02:01=Al menos no te tiraron por el váter, %1 +02:01=Sonic no podía nadar y tú tampoco, %1 +02:01=%1 prefiere jugar a Ecco the dolphin +02:01=%1 ha ido a visitar Aquaria +02:01=%1 ha encontrado la ciudad perdida de la Atlántida +02:01=Necesitas practicar más tu estilo perrito, %1 +02:01=Necesitas practicar más tu brazada, %1 +02:01=Necesitas practicar más tu estilo mariposa, %1 +02:01=%1 debería haber traído sus esquís acuáticos +02:01=A %1 no le gustan los deportes acuáticos +02:01=%1 estará haciendo burbujas para siempre +02:01=%1 no pensó que fuera tan profundo +02:01=%1 cree que el agua salada es buena para la piel +02:01=El agua salada cura las heridas, %1 +02:01=%1 paseó por la tabla +02:01=%1 se bañó +02:01=%1 se remojó +02:01=%1 está mojado, mojado, mojado +02:01=No olvides el jabón, %1 +02:01=¡No salpiques, %1! +02:01=¿Estaba fría el agua? + +; El combate empieza +02:02=¡Luchad! +02:02=¡Armado y listo! +02:02=Vamos a montar una buena fiesta +02:02=El último erizo en pie gana +02:02=¡Vamos! +02:02=¡Let's rock! +02:02=¡Al lío! +02:02=En el comienzo... +02:02=Este es el principio de algo grande +02:02=Bienvenidos a Hedgewars +02:02=Bienvenido al frente, soldado +02:02=¡Machaca al enemigo! +02:02=Que gane el mejor erizo +02:02=Victoria o muerte +02:02=Hasta la victoria, siempre +02:02=Perder no es una opción +02:02=¡Soltad los erizos de la guerra! +02:02=Hedgewars, presentado por Hedgewars.org +02:02=Tienes suerte si no juegas contra Tiyuri +02:02=Tienes suerte si no juegas contra unC0Rr +02:02=Tienes suerte si no juegas contra Nemo +02:02=Tienes suerte si no juegas contra Smaxx +02:02=Tienes suerte si no juegas contra Jessor +02:02=¡Da lo mejor! +02:02=¡El que pierda, paga! +02:02=Que empiece la batalla del milenio +02:02=Que empiece la batalla del siglo +02:02=Que empiece la batalla de la década +02:02=Que empiece la batalla del año +02:02=Que empiece la batalla del mes +02:02=Que empiece la batalla de la semana +02:02=Que empiece la batalla del día +02:02=Que empiece la batalla de la hora +02:02=¡Hazlo lo mejor que puedas! +02:02=¡Destruye al enemigo! +02:02=Buena suerte +02:02=Diviértete +02:02=Lucha limpiamente +02:02=Lucha suciamente +02:02=Lucha con honore +02:02=Si haces trampas, procura que no te pillen +02:02=Nunca abandones +02:02=Nunca te rindas +02:02=¡Que empiece la marcha! +02:02=¡Espero que estés listo para el meneo! +02:02=¡Vamos, vamos, vamos! +02:02=Tropas, ¡avanzad! +02:02=¡Dadles caña! +02:02=¡No temáis! + +; Round ends and team/clan (%1) wins +02:03=¡%1 venció! + +; Round ends in a draw +02:04=Empate + +; Botiquín +02:05=¡Ayuda en camino! +02:05=¡Médico! +02:05=¡Primeros auxilios desde el cielo! +02:05=Un buen lote de medicamentos para ti +02:05=¡Buena salud... en forma de caja! +02:05=La llamada del doctor +02:05=¡Tiritas frescas! +02:05=Vendas limpias +02:05=Esto te hará sentir mejor +02:05=¡Una poción para ti! Ups, juego equivocado +02:05=¡Un paquete para recoger! +02:05=Cógelo +02:05=Una barrita saludable +02:05=Una cura para el dolor +02:05=Posología: ¡tantos como puedas conseguir! +02:05=Envío urgente +02:05=¡Víveres! + +; Caja de armamento +02:06=¡Más armas! +02:06=¡Refuerzos! +02:06=¡Armado y listo! +02:06=Me pregunto qué arma habrá ahí dentro... +02:06=¡Víveres! +02:06=¿Qué habrá dentro? +02:06=La navidad llega antes a Hedgewars +02:06=¡Un regalito! +02:06=¡Envío especial! +02:06=No sabes qué pesadilla ha sido atravesar la aduana con esto +02:06=Juguetes destructivos del Cielo +02:06=¡Cuidado! Volátil +02:06=¡Cuidado! Inflamable +02:06=Cógelo o reviéntalo, la elección es tuya +02:06=¡Mmmmm, armas! +02:06=Una caja de poder destructivo +02:06=¡Correo aéreo! +02:06=Contenga lo que contenga esa caja, seguro que no es pizza +02:06=¡Cógelo! +02:06=Envío de armas en camino +02:06=Refuerzos en camino +02:06=¡No dejes que el enemigo te lo quite! +02:06=¡Nuevos juguetitos! +02:06=¡Una caja misteriosa! + +; Caja de herramientas +02:07=¡La hora de la herramienta! +02:07=Esto podría ser útil... +02:07=¡Herramientas! +02:07=Usa esta caja +02:07=Cuidado los de abajo +02:07=¡Más herramientas! +02:07=¡Herramientas para ti! +02:07=¡Esto te vendrá bien! +02:07=Úsalo correctamente +02:07=Guau, esta caja es pesada +02:07=Podrías necesitarlo + +; El erizo %1 pasa su turno +02:08=%1 es un muermo... +02:08=%1 ni se molesta +02:08=%1 es un erizo perezoso +02:08=%1 tiene la mente en blanco +02:08=%1 abandona +02:08=El que quiera peces debe mojarse el culo, %1 +02:08=%1 abandona vergonzosamente el frente +02:08=%1 es muy muy vago +02:08=%1 necesita un poco más de motivación +02:08=%1 es un pacifista +02:08=%1 necesita su inhalador +02:08=%1 echa una cabezada +02:08=%1 se relaja +02:08=%1 se tumba a la bartola +02:08=Ommmmmm... +02:08=%1 no tiene confianza en sí mismo +02:08=%1 decide no hacer nada en absoluto +02:08=%1 deja que el enemigo se destruya a sí mismo +02:08=%1 debe ser un muermo en las fiestas +02:08=%1 se esconde +02:08=%1 ha dejado pasar esta oportunidad +02:08=%1 ha decidido que lo mejor que puede hacer es... nada +02:08=%1 es un cobardica +02:08=Co-Co-Cococó, %1 es un gallina +02:08=¡%1 es un cobarde! +02:08=%1 está esperando a la muerte súbita +02:08=%1 no se encuentra en forma +02:08=%1 está reconsiderando el sentido de su vida +02:08=%1 nunca tuvo mucha puntería, de todas formas +02:08=%1 nunca quiso alistarse en el ejército en realidad +02:08=No nos hagas perder el tiempo, %1 +02:08=Me has decepcionado, %1 +02:08=Vamos, %1, eres capaz de hacerlo mejor +02:08=La voluntad de %1 se quebró +02:08=Por lo visto %1 tiene mejores cosas que hacer +02:08=%1 está paralizado de terror +02:08=%1 se ha dormido + +; El erizo %1 se daña únicamente a sí mismo +02:09=¡%1 debería ir al campo de tiro a practicar! +02:09=%1 se odia a sí mismo +02:09=¡%1 estaba en el lado equivocado! +02:09=%1 es un poco emo +02:09=%1 tenía el arma del revés +02:09=%1 es un poco sádico +02:09=%1 es un masoquista +02:09=%1 no tiene instinto de supervivencia +02:09=%1 la pifió +02:09=%1 la fastidió +02:09=Ese fue un tiro pésimo, %1 +02:09=%1 es demasiado descuidado como para usar armas peligrosas +02:09=%1, deberías considerar un cambio de profesión +02:09=¡Peor! ¡Tiro! ¡Historia! +02:09=¡No, no, no, %1, debes disparar AL ENEMIGO! +02:09=%1 debería estar destruyendo enemigos +02:09=%1 se acerca un poco más al suicidio +02:09=%1 le echa una mano al enemigo +02:09=Eso fue una estupidez, %1 +02:09=%1 vive con la máxima "sin dolor no hay honor" +02:09=%1 está confuso +02:09=%1 se dispara a sí mismo en su confusión +02:09=¡%1 tiene un don para hacerse daño! +02:09=¡%1 es un patoso! +02:09=%1 es torpe +02:09=%1 le acaba de demostrar al enemigo de lo que es capaz +02:09=No se puede esperar que %1 sea perfecto todo el tiempo +02:09=No te preocupes, %1, nabie es ferpecto +02:09=¡Pues claro que %1 hizo eso a propósito! +02:09=No se lo diré a nadie si tú tampoco lo haces, %1 +02:09=¡Qué vergüenza! +02:09=Seguro que nadie te ha visto, %1 +02:09=%1 necesita revisar el manual +02:09=Las armas de %1 eran obviamente defectuosas + +; Home run (usando el bate de béisbol) +02:10=¡Home Run! +02:10=Es un pájaro, es un avión... +02:10=¡Eliminado! + +; El erizo (%1) abandona (el equipo ha salido de la partida) +02:11=¡%1 tiene que irse a mimir! +02:11=¡%1 tiene que irse a la cama! +02:11=Parece que %1 está demasiado ocupado para seguir jugando +02:11=¡Teletranspórtame, Scotty! +02:11=%1 tiene que irse + +; Categorías de armamento +03:00=Arma arrojadiza +03:01=Arma arrojadiza +03:02=Artillería +03:03=Misil +03:04=Arma de fuego (múltiples disparos) +03:05=Herramienta de excavación +03:06=Acción +03:07=Herramienta de transporte +03:08=Bomba de proximidad +03:09=Arma de fuego (disparo único) +03:10=¡BUM! +03:11=¡Bonk! +03:12=Artes marciales +03:13=SIN USAR +03:14=Herramienta de transporte +03:15=Ataque por aire +03:16=Ataque por aire +03:17=Herramienta de excavación +03:18=Herramienta +03:19=Herramienta de transporte +03:20=Acción +03:21=Arma balística +03:22=¡Llámame Indiana! +03:23=Artes marciales (en serio) +03:24=¡La tarta NO ES una mentira! +03:25=Disfraz +03:26=Arma arrojadiza jugosa +03:27=Arma arrojadiza fogosa +03:28=Artillería +03:29=Artillería +03:30=Ataque por aire +03:31=Bomba radiocontrolada +03:32=Efecto temporal +03:33=Efecto temporal +03:34=Efecto temporal +03:35=Efecto temporal +03:36=Efecto temporal +03:37=Efecto temporal +03:38=Arma de fuego (disparo único) +03:39=Herramienta de transporte +03:40=Bomba incendiaria +03:41=Amigo chillón +03:42=Creo que voy a tomar una nota... +03:43=E interpretando el Cascanueces tenemos a... +03:44=Consumir preferentemente antes de 1923 +03:45=¡El poder de la ciencia! +03:46=¡Caliente caliente caliente! +03:47=¡Pégalo en un buen sitio! +03:48=Pablo clavó un clavito +03:49=Hace exactamente lo que dice +03:50=Para los amantes de los topos +03:51=Me la encontré por el suelo +03:52=SIN USAR +03:53=Tipo 40 +03:54=Herramienta +03:55=No se ve genial con esto! +03:56=Porfavor usalo o dejalo +03:57=Utilidad +03:58=Bomba de proximidad flotante. +03:59=El ultimo poder +; Descripciones de armamento ( líneas delimitadas con | ) +04:00=Ataca a tus enemigos usando una sencilla granada.|Explotará una vez el temporizador llegue a cero.|1-5: ajustar temporizador.|Atacar: mantener presionado para lanzar más lejos. +04:01=Ataca a tus enemigos usando una granada de fragmentación.|Se fragmentará en metralla explosiva|una vez el temporizador llegue a cero.|1-5: ajustar temporizador.|Atacar: mantener presionado para lanzar más lejos. +04:02=Ataca a tus enemigos usando un proyectil balístico.|¡Atención al viento, modificará su trayectoria!|Atacar: mantener presionado para lanzar más lejos. +04:03=Lanza un abejorro explosivo que buscará el objetivo marcado.|No dispares a máxima potencia para mejorar su precisión.|Ratón: seleccionar objetivo.|Atacar: mantener presionado para lanzar más lejos. +04:04=Ataca a tus enemigos usando una escopeta de dos cañones.|Las balas se dispersan, así que no necesitarás|un tiro directo para herir a tus oponentes.|Atacar: abrir fuego (dos tiros). +04:05=¡Entiérrate! Usa el martillo neumático para excavar|un pozo en el suelo y alcanzar otras áreas.|Atacar: empezar o terminar de cavar. +04:06=¿Aburrido? ¿Sin posibilidad de atacar? ¿Racionas tu munición?|¡No hay problema! ¡Adelante, pasa esta turno, gallina!|Atacar: pasa este turno sin hacer nada. +04:07=Cubre grandes distancias usando hábilmente la cuerda.|Gana inercia para empujar a otros erizos|o deja caer granadas u otras armas sobre ellos.|Atacar: lanza o suelta la cuerda.|Salto: deja caer el arma seleccionada. +04:08=Mantén alejados a tus enemigos desplegando minas|en pasadizos estrechos o justo bajo sus pies.|¡Asegúrate de alejarte rápidamente para no activarla tú mismo!|Atacar: deposita una mina ante ti. +04:09=¿No confías en tu puntería? Con la desert eagle|tienes 4 disparos para conseguir alcanzar a tu enemigo.|Atacar: abrir fuego (hasta 4 veces). +04:10=La fuerza bruta siempre es una opción. Coloca este clásico|explosivo cerca de tus enemigos y huye.|Atacar: deposita la dinamita ante ti. +04:11=¡Manda a tus enemigos lejos de ti de un buen batazo!|Acaba con ellos lanzándolos fuera del mapa o al agua.|¿O qué tal lanzarles algunas minas?|Atacar: batear cualquier cosa delante de ti. +04:12=Enfréntate cara a cara con tus enemigos|y libera el poder de tus puños sobre ellos.|Útil para lanzarlos fuera del mapa o al agua.|Atacar: ejecutar el puño de fuego. +04:13=SIN USAR +04:14=¿Te dan miedo las alturas? Nunca más con un buen paracaídas.|Se desplegará automáticamente cuando caigas suficientemente lejos.|Atacar: desplegar/replegar el paracaídas.|Cursores: controlar el descenso. +04:15=Haz llover bombas sobre tus enemigos solicitando un bombardeo aéreo.|Derecha/izquierda: determinar dirección del ataque.|Ratón: seleccionar objetivo. +04:16=Haz llover minas sobre tus enemigos solicitando un minado aéreo.|Derecha/izquierda: determinar dirección del ataque.|Ratón: seleccionar objetivo. +04:17=¿Buscas refugio? ¿Necesitas salir de una cueva?|Usa el soplete para cavar un túnel a través del terreno.|Atacar: encender/apagar el soplete. +04:18=¿Necesitas protección adicional o quieres atravesar algún abismo?|Coloca tantas vigas como quieras/puedas.|Derecha/izquierda: seleccionar tipo de viga.|Ratón: colocar viga. +04:19=Usado en el momento adecuado, el teletransporte puede ser|tu mayor aliado, ayudándote a escapar de situaciones mortales|o alcanzar víveres valiosos.|Ratón: seleccionar objetivo. +04:20=Te permite jugar este turno con otro de tus erizos.|Atacar: activar.|Tabulador: cambiar entre erizos una vez activada. +04:21=Lanza un proyectil que se fragmentará al impactar, enviando|una lluvia explosiva sobre tus enemigos.|Atacar: lanzar a máxima potencia. +04:22=¡Siéntete como Indiana Jones! El látigo es un arma muy útil|en ciertas situaciones, especialmente para deshacerte de|erizos enemigos enviándolos fuera del mapa o al agua.|Atacar: golpear cualquier cosa delante de ti. +04:23=Si no tienes nada que perder, esto puede serte útil.|Sacrifica a tu erizo lanzándolo como un cohete que despejará|cualquier cosa que encuentre en su camino, detonando al final.|Atacar: manda a tu erizo a la perdición. +04:24=¡Feliz cumpleaños! Esta tarta bípeda caminará|hasta tus enemigos para darles una fiesta sorpresa explosiva.|La tarta es capaz de atravesar casi cualquier tipo de terreno,|pero evita que se quede atascada.|Atacar: enviar la tarta de camino o hacerla detonar. +04:25=Utiliza este disfraz para seducir a tus enemigos,|haciéndoles perder la cabeza y saltar como locos hacia ti|(y de paso hacia el agua o una mina).|Atacar: disfrazarte y lanzar un beso a tus enemigos. +04:26=Lanza esta jugosa sandía a tus enemigos.|Una vez el temporizador llegue a cero se fragmentará|en rodajas deliciosamente explosivas sobre tus enemigos.|Atacar: mantener presionado para lanzar más lejos. +04:27=Haz que el fuego del averno chamusque a tus enemigos|usando esta granada infernal.|¡Aléjate todo lo que puedas de la explosión,|la onda expansiva y el fuego tienen gran alcance!|Atacar: mantener presionado para lanzar más lejos. +04:28=Una vez lanzado, este proyectil comenzará a cavar tan pronto toque tierra|y explotará al volver a salir a la superficie o al encontrar algún obstáculo,|como un erizo enemigo o una caja.|Atacar: mantener presionado para lanzar más lejos. +04:29=¡Puede parecer un juguete, pero no lo es!|El lanzapelotas dispara montones de pelotas multicolores|llenas de explosivos que rebotarán hasta tus enemigos.|Atacar: lanzar a máxima potencia.|Arriba/abajo: modificar ángulo de disparo. +04:30=Haz llover fuego sobre tus enemigos solicitando un ataque aéreo.|Con la destreza adecuada, este ataque puede erradicar grandes áreas de tierra.|Derecha/izquierda: determinar dirección del ataque.|Ratón: seleccionar objetivo. +04:31=El avión teledirigido es el arma ideal para recoger cajas|o atacar enemigos lejanos.|Cargado con 3 bombas, el avión explotará|si choca contra algo.|Atacar: lanzar el avión o dejar caer las bombas.|Cursores: controlar el avión. +04:32=¡Mucho mejor que cualquier dieta! Salta más alto y más lejos|o haz que tus enemigos vuelen incluso más lejos.|Atacar: activar. +04:33=A veces uno necesita una pequeña ayuda para acabar con sus enemigos.|Atacar: activar. +04:34=¡Na, na, na, no me tocas!|Atacar: activar. +04:35=A veces el reloj corre demasiado deprisa. Consigue un poco|de tiempo extra para finalizar tu ataque.|Atacar: activar. +04:36=Vaya, parece que tu puntería apesta. Por suerte para ti|la tecnología moderna está de tu lado.|Atacar: activar. +04:37=No le temas a la luz del día. Sólo durará un turno, pero|te permitirá absorber la fuerza vital de tus enemigos|cuando les ataques.|Atacar: activar. +04:38=El rifle de francotirador puede ser el arma más destructiva|de todo tu arsenal, pero es muy inefectiva en distancias cortas.|El daño infligido es proporcional a la distancia respecto del objetivo.|Atacar: abrir fuego (un disparo). +04:39=Vuela hasta otras partes del mapa usando un platillo volante.|Puede ser complicado de controlar, pero conseguirás llegar|a sitios que nunca hubieras imaginado accesibles.|Atacar: activar.|Cursores: acelerar en esa dirección (un golpe cada vez). +04:40=Alza un muro de fuego usando esta botella|llena de (en breve, ardiendo) líquido inflamable.|Atacar: mantener presionado para lanzar más lejos. +04:41=¡Demostrando que lo natural puede ser mejor|que lo artificial, Birdy puede no sólo|transportar tu erizo como el platillo volante,|sino también lanzar huevos envenenados a tus enemigos!|Atacar: activar/lanzar huevos.|Cursores: aletear en esa dirección. +04:42=El dispositivo portátil de portales es capaz de|transportar instantáneamente minas, armas o ¡incluso erizos!|Úsalo adecuadamente y tu campaña será un... |¡ÉXITO ALUCINANTE!|Atacar: disparar un portal.|Cambiar: alternar el color a disparar. +04:43=¡Haz un debut explosivo en el mundo del espectáculo!|Lanza un piano desde lo más alto del firmamento, pero ten cuidado...|¡alguien debe tocarlo, y eso puede costarte la vida!|Ratón: seleccionar objetivo.|F1-F9: tocar el piano. +04:44=¡No es simplemente queso, es un arma biológica!|No causará mucho daño al detonar, pero ten por seguro|que cualquiera que se acerque demasiado|a su oloroso rastro quedará gravemente intoxicado.|1-5: ajustar temporizador.|Atacar: mantener presionado para lanzar más lejos. +04:45=Al fin una utilidad para todas esas clases de física.|Dispara una devastadora onda sinusoidal que mandará|a tus enemigos al infierno matemático.|Ten cuidado, el retroceso de este arma es considerable.|Atacar: disparar. +04:46=Envuelve a tus enemigos en siseante fuego líquido.|¡Se derretirán de placer!|Atacar: activar.|Arriba/abajo: modificar trayectoria.|Izquierda/derecha: modificar potencia de fuego. +04:47=¡Dos bombas lapa, doble diversión!|Útiles para planear reacciones en cadena, atrincherarte...|¡o las dos cosas!.|Atacar: mantener presionado para lanzar más lejos (dos disparos). +04:48=¿Por qué la gente siempre la toma con los topos?|¡Golpear erizos es aún más divertido!|Un buen mazazo puede reducir en un tercio la|vida de cualquier erizo y enterrarlo completamente.|Atacar: activar. +04:49=¡Resucita a tus aliados!|Pero ten cuidado, también resucitarás a tus enemigos.|Atacar: mantener presionado para resucitar lentamente.|Arriba: acelerar resurrección. +04:50=¿Alguien está oculto bajo tierra?|¡Desentiérralos con un bombardeo perforador!|El temporizador controla la profundidad a alcanzar. +04:51=¿Qué hay más barato que el barro?|Un tiro gratis gracias a la bola de barro.|Hará que el enemigo salga volando|y escuece un poco si te entra en los ojos. +04:52=SIN USAR +04:53=Vive una trepidante aventura a través del|espacio y el tiempo mientras tus compañeros|siguen luchando en tu lugar.|Estate preparado para volver en cualquier momento,|o al llegar la Muerte súbita si te has quedado solo.|Aviso: no funciona durante la Muerte súbita,|si estás solo o si eres el rey. +04:54=Esparce un chorro de pegajoso barro.|Construye puentes, entierra enemigos o cierra túneles.|¡Ten especial cuidado de no mancharte! +04:55=Retorna a la Edad de Hielo!|Congela los erizos y vuelve el piso mas resbaloso|Salvate congelando el agua.|Ataque: activa o desactiva rayo congelador |Arriba o abajo: Continua apuntando +04:56=Puedes tirar 2 cuchillos al enemigo y bloques|pazadisos y tuneles en vez usarlos|Escalarlos! Su daño incrementa con su velocidad.|Pero se cauteloso, jugar con cuchillos es peligroso.|Attaque: Presiona para disparar con mas poder. +04:57=Construye una banda elastica muy resistente, desde donde|los erizos y otros rebotan|sin tomar daño de caida.|Izquierda/Derecha: cambiar la orientacion de la banda|Cursor: Coloca la banda en una posicion adecuada. +04:58=Este bomba de promixidad flota en el aire y sigue|los erizos que se acerquen a esto.|Su explosion es mas debil que una mina.|Ataque: Presiona para obtener mas poder. +04:59=Esta arma no esta terminada y es experimental.|Usar a su propio riesgo! +04:60=Libera una lluvia de balas encima de tu amigo!|Y pensaban que estaban a salvo|Detras de una capa triple de vigas.|Ataque: Dispara a poder maximo|Arriba/Abajo: Continua disparando. +; Game goal strings +05:00=Modos de juego +05:01=Las siguientes reglas están activas: +05:02=Posicionar el rey: elige un buen cobijo para tu rey +05:03=Baja gravedad: mira bien dónde pisas +05:04=Invulnerabilidad: todos los erizos tienen un campo de fuerza personal que los protege +05:05=Vampirismo: dañar a tus enemigos te curará a ti +05:06=Karma: compartirás parte del daño que inflijas +05:07=Rey: ¡no permitas que tu rey muera! +05:08=Posicionar erizos: los jugadores posicionan a mano su erizos por turnos antes de empezar a jugar +05:09=Artillería: afina tu puntería, los erizos no pueden moverse +05:10=Terreno indestructible: la mayoría de armas no pueden dañar el terreno de juego +05:11=Munición compartida: los equipos del mismo color comparten la munición +05:12=Minas: las minas detonarán a cabo de %1 segundo(s) +05:13=Minas: las minas detonarán al instante +05:14=Minas: las minas detonarán aleatoriamente al cabo de 0 - 3 segundos +05:15=Modificador al daño: las armas harán un %1% de su daño habitual +05:16=La salud de todos los erizos se restaura al final de cada turno +05:17=La computadora resucita al morir +05:18=Sin límite de ataques por turno +05:19=El arsenal se restaura al final de cada turno +05:20=Los erizos no comparten arsenal +05:21=Tag Team: los equipos del mismo clan se van turnando entre ellos.|Turno compartido: los equipos del mismo clan comparten la duración del turno. +05:22=Viento pesado: el viento afecta casi todo. diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Locale/hedgewars_es.ts --- a/share/hedgewars/Data/Locale/hedgewars_es.ts Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Locale/hedgewars_es.ts Tue Aug 22 08:35:46 2023 +0200 @@ -14,7 +14,7 @@ Revision %1 (%2) - + Revision %1 (%2) Visit our homepage: %1 @@ -22,7 +22,7 @@ This program is distributed under the %1. - + Este programa es distribuido bajo el 1%. GNU GPL v2 @@ -80,11 +80,11 @@ Credits - + Créditos Other people - + Otras personas %1 (alias %2) @@ -107,11 +107,11 @@ Extended Credits - + Créditos extendidos An extended credits list can be found in the CREDITS text file. - + Una lista de créditos extendidos puede encontrarse en el archivo de texto CREDITS. <a href="https://visualstudio.microsoft.com">VC++</a>: %1 @@ -119,14 +119,14 @@ Unknown Compiler: %1 - + Compilador Desconocido: %1 AbstractPage Go back - + Atrás @@ -137,23 +137,23 @@ Nick - + Apodo IP/Nick - + IP/Apodo Reason - + Razón Duration - + Duración Ok - + Ok Cancel @@ -165,15 +165,15 @@ Warning - + Alerta permanent - + permanente Ban player - + Banear Jugador Please specify an IP address. @@ -188,14 +188,14 @@ DataManager Use Default - + Usar Default FeedbackDialog View - + Ver Cancel @@ -203,15 +203,15 @@ Send Feedback - + Enviar Feedback We are always happy about suggestions, ideas, or bug reports. - + Sugerencias, ideas y reportes de fallos son bienvenidos. Send us feedback! - + Envíe sus comentarios! If you found a bug, you can see if it's already been reported here: diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Locale/pl.lua --- a/share/hedgewars/Data/Locale/pl.lua Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Locale/pl.lua Tue Aug 22 08:35:46 2023 +0200 @@ -2989,6 +2989,6 @@ ["Zombi"] = "Zombi", -- portal ["'Zooka Team"] = "Bazookinierzy", ["Zoom: [Pinch] with 2 fingers"] = "Przybliż: [Uszczypnij] dwoma palcami", -- Basic_Training_-_Movement - ["Zoom: [Rotate mouse wheel]"] = "Przyybliż: [Obróć kółkiem myszy]", -- Basic_Training_-_Movement + ["Zoom: [Rotate mouse wheel]"] = "Przybliż: [Obróć kółkiem myszy]", -- Basic_Training_-_Movement ["Zork"] = "Zork", -- A_Classic_Fairytale:dragon, A_Classic_Fairytale:family, A_Classic_Fairytale:queen } diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Locale/tips_de.xml --- a/share/hedgewars/Data/Locale/tips_de.xml Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Locale/tips_de.xml Tue Aug 22 08:35:46 2023 +0200 @@ -13,7 +13,7 @@ Hedgewars ist freie Open-Source-Software, die wir in unserer Freizeit erstellen. Falls du Probleme hast, frag uns in unseren Foren oder besuch unseren IRC-Channel! Hedgewars ist freie Open-Source-Software, die wir in unserer Freizeit erstellen. Wenn es dir gefällt, hilf uns mit einer kleinen Spende oder steuere deine eigenen Werke bei! Hedgewars ist freie Open-Source-Software, die wir in unserer Freizeit erstellen. Teile es mit deiner Familie und deinen Freunden, wie es dir gefällt! - Hedgewars ist freie Open-Source-Software, die wir in unserer Freizeit nur so zum Spaß erstellen. Triff die Entwickler auf #hedgewars! + Hedgewars ist freie Open-Source-Software, die wir in unserer Freizeit nur so zum Spaß erstellen. Triff die Entwickler auf #hedgewars! Von Zeit zu Zeit wird es offizielle Turniere geben. Bevorstehende Ereignisse werden auf https://www.hedgewars.org/ ein paar Tage im Voraus angekündigt. Hedgewars ist in vielen Sprachen verfügbar. Wenn die Übersetzung deiner Sprache zu fehlen oder veraltet zu sein scheint, nimm ruhig mit uns Kontakt auf! Hedgewars läuft auf vielen verschiedenen Betriebssystemen, unter anderem Microsoft Windows, macOS und GNU/Linux. diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Locale/tips_en.xml --- a/share/hedgewars/Data/Locale/tips_en.xml Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Locale/tips_en.xml Tue Aug 22 08:35:46 2023 +0200 @@ -13,7 +13,7 @@ Hedgewars is free software (Open Source) we create in our spare time. If you’ve got problems, ask on our forums or visit our IRC room! Hedgewars is free software (Open Source) we create in our spare time. If you like it, feel free to help us with a small donation or contribute your own work! Hedgewars is free software (Open Source) we create in our spare time. Share it with your family and friends as you like! - Hedgewars is free software (Open Source) we create in our spare time, just for fun! Meet the developers in #hedgewars! + Hedgewars is free software (Open Source) we create in our spare time, just for fun! Meet the developers in #hedgewars! From time to time there will be official tournaments. Upcoming events will be announced at https://www.hedgewars.org/ some days in advance. Hedgewars is available in many languages. If the translation in your language seems to be missing or outdated, feel free to contact us! Hedgewars can be run on lots of different operating systems including Microsoft Windows, macOS and GNU/Linux. diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Locale/tips_gd.xml --- a/share/hedgewars/Data/Locale/tips_gd.xml Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Locale/tips_gd.xml Tue Aug 22 08:35:46 2023 +0200 @@ -13,7 +13,7 @@ ’S e bathar-bog saor (Open Source) a th’ ann an Hedgewars a tha sinn a’ cruthachadh gu saor-thoileach. Ma tha duilgheadas agad, faighnich air a’ bhòrd-bhrath no tadhail air an t-seòmar IRC againn! ’S e bathar-bog saor (Open Source) a th’ ann an Hedgewars a tha sinn a’ cruthachadh gu saor-thoileach. Ma tha e a’ còrdadh riut, nach doir thu tabhartas airgid no obrach dhuinn? ’S e bathar-bog saor (Open Source) a th’ ann an Hedgewars a tha sinn a’ cruthachadh gu saor-thoileach. Co-roinn e le do theaghlach is caraidean mar a thogras tu! - ’S e bathar-bog saor (Open Source) a th’ ann an Hedgewars a tha sinn a’ cruthachadh gu saor-thoileach a cum tlachd! Coinnich ris an luchd-leasachaidh ann an #hedgewars! + ’S e bathar-bog saor (Open Source) a th’ ann an Hedgewars a tha sinn a’ cruthachadh gu saor-thoileach a cum tlachd! Coinnich ris an luchd-leasachaidh ann an #hedgewars! Bi fèill-chluiche oifigeil againn o àm gu àm. Sgaoilidh sinn brathan-naidheachd mu na tachartasan air https://www.hedgewars.org/ beagan làithean ro làimh. Tha Hedgewars ri fhaighinn ann an iomadh cànan. Ma tha an cànan agad a dhìth no an t-eadar-theangachadh ro shean, nach cuir thu fios thugainn? Gabhaidh Hedgewars a ruith air iomadh siostam-obrachaidh, a’ gabhail a-steach Microsoft Windows, MacOS agus GNU/Linux. diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Locale/tips_hu.xml --- a/share/hedgewars/Data/Locale/tips_hu.xml Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Locale/tips_hu.xml Tue Aug 22 08:35:46 2023 +0200 @@ -13,7 +13,7 @@ A Hedgewars egy szabad szoftver (nyílt forráskódú), amit a szabadidőnkben fejlesztünk. Probléma esetén kérdezz a fórumokon, vagy látogasd meg IRC szobánkat! A Hedgewars egy szabad szoftver (nyílt forráskódú), amit a szabadidőnkben fejlesztünk. Ha tetszik, nyugodtan küldj egy kis adományt, vagy add hozzá te is a munkádat! A Hedgewars egy szabad szoftver (nyílt forráskódú), amit a szabadidőnkben fejlesztünk. Nyugodtan oszd meg családoddal, barátaiddal is! - A Hedgewars egy szabad szoftver (nyílt forráskódú), amit a szabadidőnkben fejlesztünk, csak a móka kedvéért! A fejlesztőkkel a #hedgewars csatornán találkozhatsz. + A Hedgewars egy szabad szoftver (nyílt forráskódú), amit a szabadidőnkben fejlesztünk, csak a móka kedvéért! A fejlesztőkkel a #hedgewars csatornán találkozhatsz. Időről időre hivatalos bajnokságok indulnak. A közelgő eseményeket pár nappal előre bejelentjük a https://www.hedgewars.org/ webhelyen. A Hedgewars sok nyelven elérhető. Ha a te nyelved fordítása hiányzik vagy elavult, nyugodtan keress minket! A Hedgewars számos operációs rendszerre elérhető, többek között Microsoft Windowsra, macOS-re és GNU/Linuxra is. diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Locale/tips_it.xml --- a/share/hedgewars/Data/Locale/tips_it.xml Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Locale/tips_it.xml Tue Aug 22 08:35:46 2023 +0200 @@ -13,7 +13,7 @@ Hedgewars è un programma Open Source e gratuito che creiamo nel nostro tempo libero. Se hai problemi, chiedi nei nostri forum oppure visita il nostro canale IRC! Hedgewars è un programma Open Source e gratuito che creiamo nel nostro tempo libero. Se ti piace, aiutaci con una piccola donazione o contribuisci con il tuo lavoro! Hedgewars è un programma Open Source e gratuito che creiamo nel nostro tempo libero. Condividilo con tutta la famiglia e con gli amici come più ti piace! - Hedgewars è un programma Open Source e gratuito che creiamo nel nostro tempo libero. Incontra gli sviluppatori sul canale #hedgewars! + Hedgewars è un programma Open Source e gratuito che creiamo nel nostro tempo libero. Incontra gli sviluppatori sul canale #hedgewars! Di tanto in tanto ci saranno tornei ufficiali. Gli eventi saranno annunciati su https://www.hedgewars.org/ con qualche giorno di anticipo. Hedgewars è disponibile in molte lingue. Se la traduzione nella tua lingua sembra mancante o non aggiornata, sentiti libero di contattaci! Hedgewars può essere usato su molti sistemi operativi differenti come Microsoft Windows, Mac OS X e GNU/Linux. diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Locale/tips_pl.xml --- a/share/hedgewars/Data/Locale/tips_pl.xml Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Locale/tips_pl.xml Tue Aug 22 08:35:46 2023 +0200 @@ -11,7 +11,7 @@ Hedgewars jest darmową grą o otwartym kodzie, którą tworzymy w naszym wolnym czasie. Jeśli masz jakiś problem, zapytaj na naszym forum lub odwiedź nasz kanał IRC! Hedgewars jest darmową grą o otwartym kodzie, którą tworzymy w naszym wolnym czasie. Jeśli ją lubisz, wspomóż nas małą wpłatą lub wnieś w nią trochę własnej pracy! Hedgewars jest darmową grą o otwartym kodzie, którą tworzymy w naszym wolnym czasie. Jeśli tylko chcesz, rozdaj ją swojej rodzinie i kolegom! - Hedgewars jest darmową grą o otwartym kodzie, którą tworzymy w naszym wolnym czasie, tylko dla zabawy! Poznaj twórców na #hedgewars! + Hedgewars jest darmową grą o otwartym kodzie, którą tworzymy w naszym wolnym czasie, tylko dla zabawy! Poznaj twórców na #hedgewars! Od czasu do czasu będą organizowane mistrzostwa. Będą one ogłaszane z wyprzedzeniem na http://www.hedgewars.org/. Hedgewars jest dostępne w wielu językach. Jeśli brakuje tłumaczenia w twoim języku bądź jest ono niekompletne, nie bój się z nami skontaktować! Hedgewars może być uruchomione na różnych systemach operacyjnych, takich jak Microsoft Windows, Mac OS X, oraz GNU/Linux. diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Locale/tips_ru.xml --- a/share/hedgewars/Data/Locale/tips_ru.xml Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Locale/tips_ru.xml Tue Aug 22 08:35:46 2023 +0200 @@ -13,7 +13,7 @@ Hedgewars - это открытое и свободное программное обеспечение, которое мы создаём в наше свободное время. Если у вас возникают вопросы, задайте их на нашем форуме или посетите наш IRC канал! Hedgewars - это открытое и свободное программное обеспечение, которое мы создаём в наше свободное время. Если вам понравилась игра, помогите нам денежным вознаграждением или вкладом в виде вашей работы! Hedgewars - это открытое и свободное программное обеспечение, которое мы создаём в наше свободное время. Распространяйте его среди друзей и членов семьи! - Hedgewars - это открытое и свободное программное обеспечение, которое мы создаём в наше свободное время в своё удовольствие! Встретиться с разработчиками можно тут #hedgewars! + Hedgewars - это открытое и свободное программное обеспечение, которое мы создаём в наше свободное время в своё удовольствие! Встретиться с разработчиками можно тут #hedgewars! Время от времени проводятся официальные турниры. Предстоящие события анонсируются на https://www.hedgewars.org/ за несколько дней. Hedgewars доступен на многих языках. Если русский перевод устарел или содержит ошибки, сообщите нам или последнему переводчику в списке! Hedgewars запускается на множестве различных операционных систем, включая Microsoft Windows, Mac OS и Linux. diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Locale/tips_zh_CN.xml --- a/share/hedgewars/Data/Locale/tips_zh_CN.xml Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Locale/tips_zh_CN.xml Tue Aug 22 08:35:46 2023 +0200 @@ -13,7 +13,7 @@ 刺猬战争是我们在空余时间做出的开源免费软件。如果你有问题,在我们的论坛询问,或访问IRC房间! 刺猬战争是我们在空余时间做出的开源免费软件。如果你喜欢它,可以给我们捐赠,或贡献自己的工作! 刺猬战争是我们在空余时间做出的开源免费软件。如果你喜欢就和家人朋友分享它! - 刺猬战争是我们在空余时间做出的开源免费软件,只为好玩!在这里与开发人员会面 #hedgewars! + 刺猬战争是我们在空余时间做出的开源免费软件,只为好玩!在这里与开发人员会面 #hedgewars! 游戏不时会举办赛事,官网会提前公布即将举办的活动 https://www.hedgewars.org/. 刺猬战争提供多种语言,如果你使用的语言翻译缺失或过时,请联系我们! 刺猬战争能在多个操作系统上运行,包括 Microsoft Windows, macOS and GNU/Linux. diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Scripts/Multiplayer/Battalion.lua --- a/share/hedgewars/Data/Scripts/Multiplayer/Battalion.lua Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Scripts/Multiplayer/Battalion.lua Tue Aug 22 08:35:46 2023 +0200 @@ -804,7 +804,7 @@ PlaySound(sndShotgunReload) if GetRandom(100) < emptyCrateChance then - AddCaption(loc("It's empty!"), msgColor, capgrpMessage) + AddCaption(GetEngineString("TMsgStrId", sidEmptyCrate), msgColor, capgrpMessage) return elseif GetRandom(100) < bonusCrateChance then factor = 3 @@ -834,7 +834,7 @@ if GetRandom(100) < emptyCrateChance then if IsHogLocal(CurHog) then - AddCaption(loc("It's empty!"), msgColor, capgrpMessage) + AddCaption(GetEngineString("TMsgStrId", sidEmptyCrate), msgColor, capgrpMessage) end return elseif GetRandom(100) < bonusCrateChance then @@ -880,7 +880,7 @@ if GetRandom(100) < emptyCrateChance then if IsHogLocal(CurHog) then - AddCaption(loc("It's empty!"), msgColor, capgrpMessage) + AddCaption(GetEngineString("TMsgStrId", sidEmptyCrate), msgColor, capgrpMessage) end return elseif GetRandom(100) < bonusCrateChance then @@ -1532,7 +1532,7 @@ useVariantHats = params['mutate'] end - if params['strength'] ~= nil and tonumber(params['strength']) > 0 then + if params['strength'] ~= nil and tonumber(params['strength']) ~= nil and tonumber(params['strength']) > 0 then strength = tonumber(params['strength']) -- Highland if mode == 'highland' then @@ -1561,7 +1561,7 @@ end end - if params['luck'] ~= nil and tonumber(params['luck']) > 0 then + if params['luck'] ~= nil and tonumber(params['luck']) and tonumber(params['luck']) > 0 then luck = tonumber(params['luck']) healthCrateChance = div(healthCrateChance * luck, 100) diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Scripts/Multiplayer/Continental_supplies.lua --- a/share/hedgewars/Data/Scripts/Multiplayer/Continental_supplies.lua Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Scripts/Multiplayer/Continental_supplies.lua Tue Aug 22 08:35:46 2023 +0200 @@ -236,8 +236,27 @@ select_wep = "" quit_hint = "" end + local gameFlagPrepend = "" + local continentInfoPlace = loc("Continents: Select a continent after placing your hogs.") + local continentInfoNormal = loc("Continents: Select a continent at the beginning.") + local continentInfo = continentInfoNormal + if GetGameFlag(gfKing) then + gameFlagPrepend = gameFlagPrepend .. GetEngineString("TGoalStrId", gidKing).."|" + if GetGameFlag(gfPlaceHog) then + gameFlagPrepend = gameFlagPrepend .. GetEngineString("TGoalStrId", gidPlaceHog).."|" + else + gameFlagPrepend = gameFlagPrepend .. GetEngineString("TGoalStrId", gidPlaceKing).."|" + end + continentInfo = continentInfoPlace + else + if GetGameFlag(gfPlaceHog) then + gameFlagPrepend = gameFlagPrepend .. GetEngineString("TGoalStrId", gidPlaceHog).."|" + continentInfo = continentInfoPlace + end + end local general_information = - loc("Continents: Select a continent at the beginning.").."|".. + gameFlagPrepend.. + continentInfo.."|".. loc("Supplies: Each continent gives you unique weapons, specials and health.").."|".. loc("Weapon specials: Some weapons have special modes (see weapon description).").. select_wep.. @@ -1144,9 +1163,15 @@ function onGameInit() SuddenDeathTurns= SuddenDeathTurns+1 + -- Disable GameFlags that are incompatible with this game + DisableGameFlags(gfPerHogAmmo, gfSharedAmmo, gfResetWeps) end function onEndTurn() + if(TotalRounds == -1) then + -- Do nothing if placing hogs + return + end if(CS.TEAM_CONTINENT[GetHogTeamName(CurrentHedgehog)]==0) then CS.TEAM_CONTINENT[GetHogTeamName(CurrentHedgehog)]=GetRandom(#CS.CONTINENT_INFORMATION)+1 @@ -1188,7 +1213,7 @@ SetAttackState(true) --when all hogs are "placed" - if(GetCurAmmoType()~=amTeleport) + if(TotalRounds ~= -1) then --will run once when the game really starts (after placing hogs and so on if(CS.INIT_TEAMS[GetHogTeamName(CurrentHedgehog)] == nil) @@ -2142,7 +2167,11 @@ CS.PARACHUTE_IS_ON=1 elseif(GetGearType(gearUid)==gtSwitcher) then - CS.SWITCH_HOG_IS_ON=true + if not CS.GAME_STARTED then + DeleteGear(gearUid) + else + CS.SWITCH_HOG_IS_ON=true + end end end diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Scripts/Multiplayer/Mutant.lua --- a/share/hedgewars/Data/Scripts/Multiplayer/Mutant.lua Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Scripts/Multiplayer/Mutant.lua Tue Aug 22 08:35:46 2023 +0200 @@ -21,6 +21,7 @@ HedgewarsScriptLoad("/Scripts/Locale.lua") HedgewarsScriptLoad("/Scripts/Tracker.lua") HedgewarsScriptLoad("/Scripts/Params.lua") +HedgewarsScriptLoad("/Scripts/Utils.lua") --[[ MUTANT SCRIPT @@ -792,8 +793,9 @@ if not gameOver then local winner = createEndGameStats() if winner then - SendStat(siGameResult, string.format(loc("%s wins!"), winner)) - AddCaption(string.format(loc("%s wins!"), winner), capcolDefault, capgrpGameState) + local winText = formatEngineString(GetEngineString("TMsgStrId", sidWinner), winner) + SendStat(siGameResult, winText) + AddCaption(winText, capcolDefault, capgrpGameState) end gameOver = true end diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Scripts/Multiplayer/Racer.lua --- a/share/hedgewars/Data/Scripts/Multiplayer/Racer.lua Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Scripts/Multiplayer/Racer.lua Tue Aug 22 08:35:46 2023 +0200 @@ -32,6 +32,7 @@ HedgewarsScriptLoad("/Scripts/Locale.lua") HedgewarsScriptLoad("/Scripts/OfficialChallenges.lua") HedgewarsScriptLoad("/Scripts/Params.lua") +HedgewarsScriptLoad("/Scripts/Utils.lua") ------------------ -- Got Variables? @@ -191,22 +192,25 @@ TeamRope = true end if params["rounds"] ~= nil then - roundLimit = math.max(1, math.floor(tonumber(params["rounds"]))) + roundLimit = tonumber(params["rounds"]) if type(roundLimit) ~= "number" then roundLimit = 3 end + roundLimit = math.max(1, math.floor(roundLimit)) end if params["waypointradius"] ~= nil then - wpRad = math.max(WAYPOINT_RADIUS_MIN, math.floor(tonumber(params["waypointradius"]))) + wpRad = tonumber(params["waypointradius"]) if type(wpRad) ~= "number" then wpRad = 450 end + wpRad = math.max(WAYPOINT_RADIUS_MIN, math.floor(wpRad)) end if params["maxwaypoints"] ~= nil then - wpLimit = math.max(2, math.floor(tonumber(params["maxwaypoints"]))) + wpLimit = tonumber(params["maxwaypoints"]) if type(wpLimit) ~= "number" then wpLimit = 8 end + wpLimit = math.max(2, math.floor(wpLimit)) end end @@ -505,17 +509,17 @@ local roundDraw = false if #clanScores >= 2 and clanScores[1].score == clanScores[2].score and clanScores[1].score ~= MAX_TURN_TIME then roundDraw = true - SendStat(siGameResult, loc("Round draw")) + SendStat(siGameResult, GetEngineString("TMsgStrId", sidDraw)) SendStat(siCustomAchievement, loc("The teams are tied for the fastest time.")) elseif #sortedTeams >= 1 then - SendStat(siGameResult, string.format(loc("%s wins!"), sortedTeams[1].name)) + SendStat(siGameResult, formatEngineString(GetEngineString("TMsgStrId", sidWinner), sortedTeams[1].name)) SendStat(siCustomAchievement, string.format(loc("%s wins with a best time of %.1fs."), sortedTeams[1].name, (sortedTeams[1].score/1000))) for i=1,#unfinishedArray do SendStat(siCustomAchievement, unfinishedArray[i]) end else roundDraw = true - SendStat(siGameResult, loc("Round draw")) + SendStat(siGameResult, GetEngineString("TMsgStrId", sidDraw)) SendStat(siCustomAchievement, loc("Nobody managed to finish the race. What a shame!")) if specialPointsCount > 0 then SendStat(siCustomAchievement, loc("Maybe you should try an easier map next time.")) diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Scripts/Multiplayer/Space_Invasion.lua --- a/share/hedgewars/Data/Scripts/Multiplayer/Space_Invasion.lua Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Scripts/Multiplayer/Space_Invasion.lua Tue Aug 22 08:35:46 2023 +0200 @@ -2,6 +2,7 @@ HedgewarsScriptLoad("/Scripts/Locale.lua") HedgewarsScriptLoad("/Scripts/Tracker.lua") HedgewarsScriptLoad("/Scripts/Params.lua") +HedgewarsScriptLoad("/Scripts/Utils.lua") --[[ Space Invasion @@ -584,8 +585,10 @@ if lGameOver then local winnerTeam = teamStats[1].name - AddCaption(string.format(loc("%s wins!"), winnerTeam), capcolDefault, capgrpGameState) - SendStat(siGameResult, string.format(loc("%s wins!"), winnerTeam)) + local winText = formatEngineString(GetEngineString("TMsgStrId", sidWinner), winnerTeam) + + AddCaption(winText, capcolDefault, capgrpGameState) + SendStat(siGameResult, winText) for i = 1, TeamsCount do SendStat(siPointType, "!POINTS") @@ -1040,26 +1043,26 @@ function onParameters() parseParams() - if params["rounds"] ~= nil then + if params["rounds"] ~= nil and tonumber(params["rounds"]) then SI.roundLimit = math.floor(tonumber(params["rounds"])) end - if params["barrels"] ~= nil then + if params["barrels"] ~= nil and tonumber(params["barrels"]) then SI.startBarrels = math.floor(tonumber(params["barrels"])) end - if params["pings"] ~= nil then + if params["pings"] ~= nil and tonumber(params["pings"]) then SI.startRadShots = math.floor(tonumber(params["pings"])) end - if params["shield"] ~= nil then + if params["shield"] ~= nil and tonumber(params["shield"]) then SI.startShield = math.min(250-80, math.floor(tonumber(params["shield"]))) end - if params["barrelbonus"] ~= nil then + if params["barrelbonus"] ~= nil and tonumber(params["barrelbonus"]) then SI.barrelBonus = math.floor(tonumber(params["barrelbonus"])) end - if params["shieldbonus"] ~= nil then + if params["shieldbonus"] ~= nil and tonumber(params["shieldbonus"]) then SI.shieldBonus = math.floor(tonumber(params["shieldbonus"])) end - if params["timebonus"] ~= nil then + if params["timebonus"] ~= nil and tonumber(params["timebonus"]) then SI.timeBonus = math.floor(tonumber(params["timebonus"])) end if params["forcetheme"] == "false" then diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Scripts/Multiplayer/TechRacer.lua --- a/share/hedgewars/Data/Scripts/Multiplayer/TechRacer.lua Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Scripts/Multiplayer/TechRacer.lua Tue Aug 22 08:35:46 2023 +0200 @@ -470,17 +470,18 @@ local roundDraw = false if #clanScores >= 2 and clanScores[1].score == clanScores[2].score and clanScores[1].score ~= MAX_TURN_TIME then roundDraw = true - SendStat(siGameResult, loc("Round draw")) + SendStat(siGameResult, GetEngineString("TMsgStrId", sidDraw)) SendStat(siCustomAchievement, loc("The teams are tied for the fastest time.")) elseif #sortedTeams >= 1 then - SendStat(siGameResult, string.format(loc("%s wins!"), sortedTeams[1].name)) + + SendStat(siGameResult, formatEngineString(GetEngineString("TMsgStrId", sidWinner), sortedTeams[1].name)) SendStat(siCustomAchievement, string.format(loc("%s wins with a best time of %.1fs."), sortedTeams[1].name, (sortedTeams[1].score/1000))) for i=1,#unfinishedArray do SendStat(siCustomAchievement, unfinishedArray[i]) end else roundDraw = true - SendStat(siGameResult, loc("Round draw")) + SendStat(siGameResult, GetEngineString("TMsgStrId", sidDraw)) SendStat(siCustomAchievement, loc("Nobody managed to finish the race. What a shame!")) SendStat(siCustomAchievement, loc("Maybe you should try an easier TechRacer map.")) end @@ -682,9 +683,10 @@ roundLimit = tonumber(params["rounds"]) - if (roundLimit == 0) or (roundLimit == nil) then + if roundLimit == nil then roundLimit = 3 end + roundLimit = math.max(1, math.floor(roundLimit)) if mapID == nil then mapID = 2 + GetRandom(7) diff -r 772a43d88e6b -r 2146cb7be36f share/hedgewars/Data/Scripts/Utils.lua --- a/share/hedgewars/Data/Scripts/Utils.lua Fri Mar 10 11:42:25 2023 +0100 +++ b/share/hedgewars/Data/Scripts/Utils.lua Tue Aug 22 08:35:46 2023 +0200 @@ -127,6 +127,22 @@ end end +-- Insert parameters %1 to %9 into an engine string and returns the result. +-- * text: engine string with parameters (from GetEngineString) +-- * ...: Arguments to insert into the string. The number of arguments MUST match +-- the number of available arguments of the engine string +-- +-- Example: formatEngineString(GetEngineString("TMsgStrId", sidWinner), "My Team") +-- to create a string showing the winning team. +function formatEngineString(text, ...) + local input = text + for i=1, 9 do + text = string.gsub(text, "%%"..i, "%%s") + end + text = string.format(text, ...) + return text +end + --[[ GLOBAL VARIABLES ]] -- Shared common land color values for land sprites. diff -r 772a43d88e6b -r 2146cb7be36f tools/build_vcpkg.bat --- a/tools/build_vcpkg.bat Fri Mar 10 11:42:25 2023 +0100 +++ b/tools/build_vcpkg.bat Tue Aug 22 08:35:46 2023 +0200 @@ -55,7 +55,7 @@ echo Running cmake... set ERRORLEVEL= -cmake . -DCMAKE_TOOLCHAIN_FILE="%VCPKG_PATH%\scripts\buildsystems\vcpkg.cmake" -G"NMake Makefiles" %CROSS_COMPILE_FLAG% %BUILD_SERVER_FLAG% "%PREFIX_FLAG%" -DCMAKE_BUILD_TYPE="%BUILD_TYPE%" -DSDL2_BUILDING_LIBRARY=1 +cmake . -DCMAKE_TOOLCHAIN_FILE="%VCPKG_PATH%\scripts\buildsystems\vcpkg.cmake" -G"NMake Makefiles" %CROSS_COMPILE_FLAG% %BUILD_SERVER_FLAG% "%PREFIX_FLAG%" -DCMAKE_BUILD_TYPE="%BUILD_TYPE%" -DSDL2_BUILDING_LIBRARY=1 -DNOVIDEOREC=1 if %ERRORLEVEL% NEQ 0 goto exitpoint diff -r 772a43d88e6b -r 2146cb7be36f tools/pas2c/PascalBasics.hs --- a/tools/pas2c/PascalBasics.hs Fri Mar 10 11:42:25 2023 +0100 +++ b/tools/pas2c/PascalBasics.hs Tue Aug 22 08:35:46 2023 +0200 @@ -2,7 +2,7 @@ module PascalBasics where import Text.Parsec.Combinator -import Text.Parsec.Char +import Text.Parsec.Char hiding (string') import Text.Parsec.Prim import Text.Parsec.Token import Text.Parsec.Language diff -r 772a43d88e6b -r 2146cb7be36f tools/pas2c/PascalParser.hs --- a/tools/pas2c/PascalParser.hs Fri Mar 10 11:42:25 2023 +0100 +++ b/tools/pas2c/PascalParser.hs Tue Aug 22 08:35:46 2023 +0200 @@ -4,7 +4,7 @@ ) where -import Text.Parsec +import Text.Parsec hiding (string') import Text.Parsec.Token import Text.Parsec.Expr import Control.Monad diff -r 772a43d88e6b -r 2146cb7be36f tools/pas2c/PascalPreprocessor.hs --- a/tools/pas2c/PascalPreprocessor.hs Fri Mar 10 11:42:25 2023 +0100 +++ b/tools/pas2c/PascalPreprocessor.hs Tue Aug 22 08:35:46 2023 +0200 @@ -1,7 +1,7 @@ {-# LANGUAGE ScopedTypeVariables #-} module PascalPreprocessor where -import Text.Parsec +import Text.Parsec hiding (string') import Control.Monad.IO.Class import Control.Monad import System.IO diff -r 772a43d88e6b -r 2146cb7be36f tools/rc/convert.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/rc/convert.sh Tue Aug 22 08:35:46 2023 +0200 @@ -0,0 +1,20 @@ +#!/usr/bin/env sh + +ls ../rc || exit + +rm -rdfv build engine +mkdir -p build engine +cd build +cmake -DNOSERVER=on -DBUILD_ENGINE_C=on -DLUA_SYSTEM=on -DNOVIDEOREC=off ../../../ +cmake --build . --target engine_c + +# this one you can get from pip: pip install scan-build +intercept-build cmake --build . --target hwengine +c2rust transpile --emit-build-files --emit-modules --reduce-type-annotations --binary hwengine compile_commands.json --output-dir=../engine + +cd ../engine +sed -i 's/f128.*//g' Cargo.toml +sed -i 's/extern crate f128.*//g' lib.rs +sed -i 's/mod src {/mod src{\npub mod to_f64;/g' lib.rs +find -type f -name '*.rs' -exec sed -i 's/f128/f64/g' {} \; -exec sed -i 's/f64::f64/f64/g' {} \; -exec sed -i 's/use ::f64;/use crate::src::to_f64::to_f64;/g' {} \; -exec sed -i 's/f64::new/to_f64/g' {} \; +cp ../to_f64.rs src/ diff -r 772a43d88e6b -r 2146cb7be36f tools/rc/to_f64.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/rc/to_f64.rs Tue Aug 22 08:35:46 2023 +0200 @@ -0,0 +1,4 @@ +pub fn to_f64>(v: T) -> f64 { + v.into() +} +