Compare commits

...

8 commits

Author SHA1 Message Date
Freddie W 9a8cab46aa
Merge 7c45688fd2 into 11fd82077a 2024-09-07 20:18:05 +02:00
Jupeyy 11fd82077a
Merge pull request #8913 from Robyt3/Video-Stop-ASAN-Fix
Fix heap-use-after-free in `CVideo::Stop`
2024-09-07 16:17:29 +00:00
Dennis Felsing 3d30ce4bf2
Merge pull request #8817 from Robyt3/Client-Start-Menu-Console
Add icon button to open console in bottom right of start menu
2024-09-07 17:19:15 +02:00
Robert Müller 9e0ba8a91f Fix heap-use-after-free in CVideo::Stop
The `delete ms_pCurrentVideo` deletes the current video instance (`this`) so the subsequent write to `m_Stopped` was invalid.

Closes #8899.
2024-09-07 16:57:27 +02:00
Robert Müller c89509bc4b Add icon button to open console in bottom right of start menu
Add a button with the "terminal" icon in the bottom right of the start menu to open the local console to ensure that the local console is usable also when no physical keyboard (with F-keys) is available.
2024-09-07 13:12:30 +02:00
Dennis Felsing 306d3c7b58
Merge pull request #8910 from Robyt3/Android-Manifest-Flags-SDL
Adopt changes to `AndroidManifest.xml` from SDL sample project
2024-09-07 10:29:41 +00:00
Robert Müller 9832288983 Adopt changes to AndroidManifest.xml from SDL sample project
Specify `android:installLocation="auto"` so the app can be installed on and move to the external storage.

Specify optional features which the app may use (touchscreen, game controller, external mouse).

Specify `android:preferMinimalPostProcessing="true"` so lower latency HDMI mode is enabled when available. Specify `android:hardwareAccelerated="true"` for consistency (it is already the default setting).

Specify same `android:configChanges` and `android:alwaysRetainTaskState` values as SDL to avoid potential bugs due to inconsistency with what the `SDLActivity` expects.

See f5ed158d1f/android-project/app/src/main/AndroidManifest.xml
2024-09-07 12:09:41 +02:00
TsFreddie 7c45688fd2 Add spectator cursor 2024-08-28 15:48:52 +08:00
13 changed files with 200 additions and 17 deletions

View file

@ -378,6 +378,15 @@ Objects = [
NetEventEx("MapSoundWorld:Common", "map-sound-world@netevent.ddnet.org", [
NetIntAny("m_SoundId"),
]),
# Spectating cursor
NetObjectEx("SpecCursor", "spec-cursor@netobj.ddnet.org", [
NetIntAny("m_Weapon"),
NetIntAny("m_TargetX"),
NetIntAny("m_TargetY"),
NetIntAny("m_DeltaPrevTargetX"),
NetIntAny("m_DeltaPrevTargetY"),
]),
]
Messages = [

View file

@ -1,15 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:installLocation="auto">
<!-- Vulkan 1.1.0 is used if supported -->
<uses-feature
android:name="android.hardware.vulkan.version"
android:required="false"
android:version="0x00401000" />
<!-- android:glEsVersion is not specified as OpenGL ES 1.0 is supported as fallback -->
<!-- Only playable in landscape mode -->
<uses-feature
android:name="android.hardware.screen.landscape"
android:required="true" />
<!-- Touchscreen support -->
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<!-- Game controller support -->
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false" />
<uses-feature
android:name="android.hardware.gamepad"
android:required="false" />
<uses-feature
android:name="android.hardware.usb.host"
android:required="false" />
<!-- External mouse input events -->
<uses-feature
android:name="android.hardware.type.pc"
android:required="false" />
<!-- Teeworlds does broadcasts over local networks -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
@ -25,17 +50,24 @@
android:isGame="true"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:hardwareAccelerated="true">
<activity
android:name="org.ddnet.client.NativeMain"
android:alwaysRetainTaskState="true"
android:exported="true"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
android:preferMinimalPostProcessing="true"
android:screenOrientation="landscape"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Let Android know that we can handle some USB devices and should receive this event -->
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="DDNet" />

View file

@ -283,6 +283,7 @@ void CVideo::Pause(bool Pause)
void CVideo::Stop()
{
dbg_assert(!m_Stopped, "Already stopped");
m_Stopped = true;
m_pGraphics->WaitForIdle();
@ -341,8 +342,6 @@ void CVideo::Stop()
pSound->PauseAudioDevice();
delete ms_pCurrentVideo;
pSound->UnpauseAudioDevice();
m_Stopped = true;
}
void CVideo::NextVideoFrameThread()

View file

@ -73,6 +73,9 @@ MACRO_CONFIG_INT(ClShowfps, cl_showfps, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE,
MACRO_CONFIG_INT(ClShowpred, cl_showpred, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame prediction time in milliseconds")
MACRO_CONFIG_INT(ClEyeWheel, cl_eye_wheel, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show eye wheel along together with emotes")
MACRO_CONFIG_INT(ClEyeDuration, cl_eye_duration, 999999, 1, 999999, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How long the eyes emotes last")
MACRO_CONFIG_INT(ClSpecCursor, cl_spec_cursor, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable the cursor of spectating player if available")
MACRO_CONFIG_INT(ClSpecCursorInterp, cl_spec_cursor_interp, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Interpolate the cursor of spectating player")
MACRO_CONFIG_INT(ClSpecCursorDemo, cl_spec_cursor_demo, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show cursor during demo playback if available")
MACRO_CONFIG_INT(ClAirjumpindicator, cl_airjumpindicator, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show the air jump indicator")
MACRO_CONFIG_INT(ClThreadsoundloading, cl_threadsoundloading, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Load sound files threaded")

View file

@ -93,6 +93,7 @@ MAYBE_UNUSED static const char *FONT_ICON_EARTH_AMERICAS = "\xEF\x95\xBD";
MAYBE_UNUSED static const char *FONT_ICON_NETWORK_WIRED = "\xEF\x9B\xBF";
MAYBE_UNUSED static const char *FONT_ICON_LIST_UL = "\xEF\x83\x8A";
MAYBE_UNUSED static const char *FONT_ICON_INFO = "\xEF\x84\xA9";
MAYBE_UNUSED static const char *FONT_ICON_TERMINAL = "\xEF\x84\xA0";
MAYBE_UNUSED static const char *FONT_ICON_SLASH = "\xEF\x9C\x95";
MAYBE_UNUSED static const char *FONT_ICON_PLAY = "\xEF\x81\x8B";

View file

@ -160,8 +160,6 @@ class CGameConsole : public CComponent
static const ColorRGBA ms_SearchHighlightColor;
static const ColorRGBA ms_SearchSelectedColor;
void Toggle(int Type);
static void PossibleCommandsRenderCallback(int Index, const char *pStr, void *pUser);
static void ConToggleLocalConsole(IConsole::IResult *pResult, void *pUserData);
static void ConToggleRemoteConsole(IConsole::IResult *pResult, void *pUserData);
@ -196,6 +194,7 @@ public:
virtual bool OnInput(const IInput::CEvent &Event) override;
void Prompt(char (&aPrompt)[32]);
void Toggle(int Type);
bool IsClosed() { return m_ConsoleState == CONSOLE_CLOSED; }
};
#endif

View file

@ -585,16 +585,35 @@ void CHud::RenderTeambalanceWarning()
void CHud::RenderCursor()
{
if(!m_pClient->m_Snap.m_pLocalCharacter || Client()->State() == IClient::STATE_DEMOPLAYBACK)
int CurWeapon;
vec2 TargetPos;
if(Client()->State() != IClient::STATE_DEMOPLAYBACK && m_pClient->m_Snap.m_pLocalCharacter)
{
// render local cursor
CurWeapon = m_pClient->m_Snap.m_pLocalCharacter->m_Weapon % NUM_WEAPONS;
TargetPos = m_pClient->m_Controls.m_aTargetPos[g_Config.m_ClDummy];
}
else if(g_Config.m_ClSpecCursor && m_pClient->m_Snap.m_pSpecCursor && m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_SpectatorId != SPEC_FREEVIEW)
{
// render spec cursor
CurWeapon = m_pClient->m_Snap.m_pSpecCursor->m_Weapon % NUM_WEAPONS;
TargetPos = m_pClient->m_Snap.m_SpecInfo.m_Position + m_pClient->m_Snap.m_DisplayCursorPos;
}
else if(g_Config.m_ClSpecCursorDemo && m_pClient->m_Snap.m_pSpecCursor && Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_Snap.m_pLocalCharacter)
{
CurWeapon = m_pClient->m_Snap.m_pLocalCharacter->m_Weapon % NUM_WEAPONS;
TargetPos = m_pClient->m_LocalCharacterPos + m_pClient->m_Snap.m_DisplayCursorPos;
}
else
{
return;
}
RenderTools()->MapScreenToInterface(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y);
// render cursor
int CurWeapon = m_pClient->m_Snap.m_pLocalCharacter->m_Weapon % NUM_WEAPONS;
Graphics()->SetColor(1.f, 1.f, 1.f, 1.f);
Graphics()->TextureSet(m_pClient->m_GameSkin.m_aSpriteWeaponCursors[CurWeapon]);
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_aCursorOffset[CurWeapon], m_pClient->m_Controls.m_aTargetPos[g_Config.m_ClDummy].x, m_pClient->m_Controls.m_aTargetPos[g_Config.m_ClDummy].y);
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_aCursorOffset[CurWeapon], TargetPos.x, TargetPos.y);
}
void CHud::PrepareAmmoHealthAndArmorQuads()

View file

@ -17,6 +17,8 @@
#include "menus.h"
using namespace FontIcons;
void CMenus::RenderStartMenu(CUIRect MainView)
{
GameClient()->m_MenuBackground.ChangePosition(CMenuBackground::POS_START);
@ -186,13 +188,27 @@ void CMenus::RenderStartMenu(CUIRect MainView)
}
// render version
CUIRect VersionUpdate, CurVersion;
MainView.HSplitBottom(20.0f, nullptr, &VersionUpdate);
VersionUpdate.VSplitRight(50.0f, &CurVersion, nullptr);
VersionUpdate.VMargin(VMargin, &VersionUpdate);
CUIRect CurVersion, ConsoleButton;
MainView.HSplitBottom(45.0f, nullptr, &CurVersion);
CurVersion.VSplitRight(40.0f, &CurVersion, nullptr);
CurVersion.HSplitTop(20.0f, &ConsoleButton, &CurVersion);
CurVersion.HSplitTop(5.0f, nullptr, &CurVersion);
ConsoleButton.VSplitRight(40.0f, nullptr, &ConsoleButton);
Ui()->DoLabel(&CurVersion, GAME_RELEASE_VERSION, 14.0f, TEXTALIGN_MR);
static CButtonContainer s_ConsoleButton;
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
if(DoButton_Menu(&s_ConsoleButton, FONT_ICON_TERMINAL, 0, &ConsoleButton, nullptr, IGraphics::CORNER_ALL, 5.0f, 0.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.1f)))
{
GameClient()->m_GameConsole.Toggle(CGameConsole::CONSOLETYPE_LOCAL);
}
TextRender()->SetRenderFlags(0);
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
CUIRect VersionUpdate;
MainView.HSplitBottom(20.0f, nullptr, &VersionUpdate);
VersionUpdate.VMargin(VMargin, &VersionUpdate);
#if defined(CONF_AUTOUPDATE)
CUIRect UpdateButton;
VersionUpdate.VSplitRight(100.0f, &VersionUpdate, &UpdateButton);

View file

@ -718,6 +718,8 @@ void CGameClient::UpdatePositions()
if(!m_MultiViewActivated && m_MultiView.m_IsInit)
ResetMultiView();
UpdateSpectatorCursor();
UpdateRenderedCharacters();
}
@ -1831,6 +1833,11 @@ void CGameClient::OnNewSnapshot()
m_aSwitchStateTeam[g_Config.m_ClDummy] = -1;
GotSwitchStateTeam = true;
}
else if(Item.m_Type == NETOBJTYPE_SPECCURSOR)
{
m_Snap.m_pSpecCursor = (const CNetObj_SpecCursor *)Item.m_pData;
m_Snap.m_pPrevSpecCursor = (const CNetObj_SpecCursor *)Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_SPECCURSOR, Item.m_Id);
}
}
}
@ -2985,6 +2992,55 @@ void CGameClient::UpdatePrediction()
m_GameWorld.NetObjEnd();
}
void CGameClient::UpdateSpectatorCursor()
{
if(m_Snap.m_pSpecCursor)
{
const float IntraTickSincePrev = Client()->IntraGameTickSincePrev(g_Config.m_ClDummy);
// Decode previous cursor position from current snapshot
const int TargetX = m_Snap.m_pSpecCursor->m_TargetX;
const int TargetY = m_Snap.m_pSpecCursor->m_TargetY;
const int PrevTargetX = TargetX + m_Snap.m_pSpecCursor->m_DeltaPrevTargetX;
const int PrevTargetY = TargetY + m_Snap.m_pSpecCursor->m_DeltaPrevTargetY;
int PrevPrevTargetX, PrevPrevTargetY;
int PrevPrevPrevTargetX, PrevPrevPrevTargetY;
if(m_Snap.m_pPrevSpecCursor)
{
PrevPrevTargetX = m_Snap.m_pPrevSpecCursor->m_TargetX;
PrevPrevTargetY = m_Snap.m_pPrevSpecCursor->m_TargetY;
PrevPrevPrevTargetX = PrevPrevTargetX + m_Snap.m_pPrevSpecCursor->m_DeltaPrevTargetX;
PrevPrevPrevTargetY = PrevPrevTargetY + m_Snap.m_pPrevSpecCursor->m_DeltaPrevTargetY;
}
else
{
PrevPrevTargetX = PrevTargetX;
PrevPrevTargetY = PrevTargetY;
PrevPrevPrevTargetX = PrevTargetX;
PrevPrevPrevTargetY = PrevTargetX;
}
if(IntraTickSincePrev <= 1.0f)
{
float Intra = g_Config.m_ClSpecCursorInterp ? IntraTickSincePrev : 1.0f;
m_Snap.m_DisplayCursorPos = mix(vec2(PrevPrevPrevTargetX, PrevPrevPrevTargetY), vec2(PrevPrevTargetX, PrevPrevTargetY), Intra);
}
else if(IntraTickSincePrev <= 2.0f)
{
float Intra = g_Config.m_ClSpecCursorInterp ? IntraTickSincePrev - 1.0f : 1.0f;
m_Snap.m_DisplayCursorPos = mix(vec2(PrevPrevTargetX, PrevPrevTargetY), vec2(PrevTargetX, PrevTargetY), Intra);
}
else
{
float Intra = g_Config.m_ClSpecCursorInterp ? IntraTickSincePrev - 2.0f : 1.0f;
m_Snap.m_DisplayCursorPos = mix(vec2(PrevTargetX, PrevTargetY), vec2(TargetX, TargetY), Intra);
}
}
}
void CGameClient::UpdateRenderedCharacters()
{
for(int i = 0; i < MAX_CLIENTS; i++)

View file

@ -313,6 +313,8 @@ public:
const CNetObj_PlayerInfo *m_pLocalInfo;
const CNetObj_SpectatorInfo *m_pSpectatorInfo;
const CNetObj_SpectatorInfo *m_pPrevSpectatorInfo;
const CNetObj_SpecCursor *m_pSpecCursor;
const CNetObj_SpecCursor *m_pPrevSpecCursor;
const CNetObj_Flag *m_apFlags[2];
const CNetObj_GameInfo *m_pGameInfoObj;
const CNetObj_GameData *m_pGameDataObj;
@ -338,6 +340,9 @@ public:
vec2 m_Position;
} m_SpecInfo;
// cursor data
vec2 m_DisplayCursorPos;
//
struct CCharacterInfo
{
@ -794,6 +799,7 @@ private:
int m_aShowOthers[NUM_DUMMIES];
void UpdatePrediction();
void UpdateSpectatorCursor();
void UpdateRenderedCharacters();
int m_aLastUpdateTick[MAX_CLIENTS] = {0};

View file

@ -1143,6 +1143,24 @@ void CCharacter::SnapCharacter(int SnappingClient, int Id)
}
}
void CCharacter::SnapSpecCursor(int SnappingClient)
{
CNetObj_SpecCursor *pCursorInfo = static_cast<CNetObj_SpecCursor *>(Server()->SnapNewItem(NETOBJTYPE_SPECCURSOR, SnappingClient, sizeof(CNetObj_SpecCursor)));
if(pCursorInfo)
{
pCursorInfo->m_Weapon = GetActiveWeapon();
pCursorInfo->m_TargetX = m_Input.m_TargetX;
pCursorInfo->m_TargetY = m_Input.m_TargetY;
/*
ensure info density at SERVER_TICK_SPEED even if sv_high_bandwidth is 0
*/
pCursorInfo->m_DeltaPrevTargetX = m_PrevInput.m_TargetX - m_Input.m_TargetX;
pCursorInfo->m_DeltaPrevTargetY = m_PrevInput.m_TargetY - m_Input.m_TargetY;
}
}
bool CCharacter::CanSnapCharacter(int SnappingClient)
{
if(SnappingClient == SERVER_DEMO_CLIENT)

View file

@ -100,6 +100,8 @@ public:
void AddVelocity(vec2 Addition);
void ApplyMoveRestrictions();
void SnapSpecCursor(int SnappingClient);
private:
// player controlling this character
class CPlayer *m_pPlayer;

View file

@ -410,6 +410,29 @@ void CPlayer::Snap(int SnappingClient)
}
}
if(m_ClientId == SnappingClient && !Server()->IsSixup(SnappingClient))
{
/*
Snap spectator cursors for local players by default.
The information is not visible to local player unless they recorded demo, which the spectator cursor will act as local player's cursor.
*/
CPlayer *pCursorOwner = this;
if(m_Team == TEAM_SPECTATORS || IsPaused())
{
pCursorOwner = m_SpectatorId != SPEC_FREEVIEW ? GameServer()->m_apPlayers[m_SpectatorId] : NULL;
}
if(pCursorOwner)
{
CCharacter *pCursorOwnerChar = pCursorOwner->GetCharacter();
if(pCursorOwnerChar && pCursorOwnerChar->IsAlive())
{
pCursorOwnerChar->SnapSpecCursor(m_ClientId);
}
}
}
CNetObj_DDNetPlayer *pDDNetPlayer = Server()->SnapNewItem<CNetObj_DDNetPlayer>(id);
if(!pDDNetPlayer)
return;