@@ -113,6 +113,8 @@ local UserLevelToImageConfFunction
113113local votedUsers = {} -- 2023-06-29 FB: ToDo: Does not get reset, if user leaves battle during vote, but has no impact
114114local usersAllowedToVote = {}
115115
116+ local playerNotes
117+
116118---- ----------------------------------------------------------------------------
117119---- ----------------------------------------------------------------------------
118120-- Globally Applicable Utilities
@@ -357,6 +359,128 @@ local function GetUserClanImage(userName, userControl)
357359 return file , needDownload
358360end
359361
362+ local function TrimPlayerNote (note )
363+ if type (note ) ~= " string" then
364+ return " "
365+ end
366+ return note :gsub (" ^%s+" , " " ):gsub (" %s+$" , " " ):sub (1 , 100 )
367+ end
368+
369+ local function GetPlayerNoteKey (userName , userInfo )
370+ userInfo = userInfo or {}
371+ if userInfo .accountID ~= nil and tostring (userInfo .accountID ) ~= " " then
372+ return " account_" .. tostring (userInfo .accountID )
373+ end
374+ return " name_" .. tostring (userName or " " )
375+ end
376+
377+ local function CleanPlayerNotes (rawNotes )
378+ local cleanNotes = {}
379+ if type (rawNotes ) ~= " table" then
380+ return cleanNotes
381+ end
382+
383+ for key , value in pairs (rawNotes ) do
384+ if type (key ) == " string" and type (value ) == " string" then
385+ local cleanNote = TrimPlayerNote (value )
386+ if cleanNote ~= " " then
387+ cleanNotes [key ] = cleanNote
388+ end
389+ end
390+ end
391+ return cleanNotes
392+ end
393+
394+ local function SavePlayerNotes ()
395+ local notesFile = io.open (" playerNotes.json" , " w" )
396+ if not notesFile then
397+ Spring .Echo (" Unable to save player notes to playerNotes.json" )
398+ return
399+ end
400+ notesFile :write (Json .encode (playerNotes or {}))
401+ notesFile :close ()
402+ end
403+
404+ local function LoadPlayerNotes ()
405+ if playerNotes then
406+ return playerNotes
407+ end
408+
409+ playerNotes = {}
410+ if VFS .FileExists (" playerNotes.json" ) then
411+ local rawNotes = VFS .LoadFile (" playerNotes.json" )
412+ if rawNotes and rawNotes ~= " " then
413+ local success , decodedNotes = pcall (Json .decode , rawNotes )
414+ if success then
415+ playerNotes = CleanPlayerNotes (decodedNotes )
416+ else
417+ Spring .Echo (" Unable to read player notes from playerNotes.json" )
418+ end
419+ end
420+ end
421+
422+ return playerNotes
423+ end
424+
425+ local function GetPlayerNote (userName , userInfo )
426+ local notes = LoadPlayerNotes ()
427+ local noteKey = GetPlayerNoteKey (userName , userInfo )
428+ local note = notes [noteKey ]
429+ if (not note or note == " " ) and userInfo and userInfo .accountID ~= nil then
430+ note = notes [" name_" .. tostring (userName or " " )]
431+ end
432+ if type (note ) == " string" and note ~= " " then
433+ return note
434+ end
435+ end
436+
437+ local function SetPlayerNote (userName , userInfo , note )
438+ local notes = LoadPlayerNotes ()
439+ local cleanNote = TrimPlayerNote (note )
440+ if type (note ) == " string" and # (note :gsub (" ^%s+" , " " ):gsub (" %s+$" , " " )) > 100 and WG .Chobby and WG .Chobby .InformationPopup then
441+ WG .Chobby .InformationPopup (" Player notes are limited to 100 characters. Your note was shortened to 100 characters." , {width = 420 , height = 180 })
442+ end
443+ local noteKey = GetPlayerNoteKey (userName , userInfo )
444+ local nameNoteKey = " name_" .. tostring (userName or " " )
445+ if cleanNote ~= " " then
446+ notes [noteKey ] = cleanNote
447+ if nameNoteKey ~= noteKey then
448+ notes [nameNoteKey ] = nil
449+ end
450+ else
451+ notes [noteKey ] = nil
452+ notes [nameNoteKey ] = nil
453+ end
454+
455+ -- Client-side only: persisted to a local file and never sent through lobby.
456+ SavePlayerNotes ()
457+ end
458+
459+ local function OpenPlayerNotesWindow (userName , userInfo )
460+ if not WG .TextEntryWindow then
461+ if WG .Chobby and WG .Chobby .InformationPopup then
462+ WG .Chobby .InformationPopup (" Text entry is not available." )
463+ end
464+ return
465+ end
466+
467+ WG .TextEntryWindow .CreateTextEntryWindow ({
468+ defaultValue = GetPlayerNote (userName , userInfo ) or " " ,
469+ caption = " Add Player Notes" ,
470+ labelCaption = " Local note for " .. userName .. " . 100 character limit. Leave blank to remove the note." ,
471+ hint = " Enter a local player note, up to 100 characters" ,
472+ height = 260 ,
473+ width = 520 ,
474+ oklabel = " Save" ,
475+ OnAccepted = function (newNote )
476+ SetPlayerNote (userName , userInfo , newNote )
477+ end ,
478+ OnOpen = function (editBox )
479+ editBox :SelectAll ()
480+ end
481+ })
482+ end
483+
360484local function GetUserComboBoxOptions (userName , isInBattle , control , showTeamColor , showSide )
361485 local Configuration = WG .Chobby .Configuration
362486 local info = control .lobby :GetUser (userName ) or {}
@@ -400,6 +524,7 @@ local function GetUserComboBoxOptions(userName, isInBattle, control, showTeamCol
400524 if bs .aiLib then comboOptions [# comboOptions + 1 ] = " Clone AI" end
401525 if bs .aiLib and bs .owner == myUserName and isInBattle then comboOptions [# comboOptions + 1 ] = " Remove" end
402526 if not itsme and not info .isBot and not bs .aiLib then comboOptions [# comboOptions + 1 ] = " Report User" end
527+ if not (info .isBot or bs .aiLib ) then comboOptions [# comboOptions + 1 ] = " Add Player Notes" end
403528 comboOptions [# comboOptions + 1 ] = " Copy Name"
404529 if (iAmBoss or iPlay ) and not (control .isSingleplayer or bs .aiLib or info .isBot ) and isInBattle then comboOptions [# comboOptions + 1 ] = " \255\128\128\128 " .. " --------------"
405530 comboOptions [# comboOptions + 1 ] = isBoss and " Disable Boss" or " Make Boss" end
@@ -1285,6 +1410,9 @@ local function GetUserControls(userName, opts)
12851410 local chatWindow = WG .Chobby .interfaceRoot .OpenPrivateChat (userName )
12861411 elseif selectedName == " Copy Name" then
12871412 Spring .SetClipboard (userName )
1413+ elseif selectedName == " Add Player Notes" then
1414+ local latestUserInfo = userControls .lobby :GetUser (userName ) or userInfo or {}
1415+ OpenPlayerNotesWindow (userName , latestUserInfo )
12881416 elseif selectedName == " Kickban" then
12891417 local function YesFunc ()
12901418 lobby :SayBattle (" !kickban " .. userName )
@@ -2004,7 +2132,9 @@ end
20042132local userHandler = {
20052133 CountryShortnameToFlag = CountryShortnameToFlag ,
20062134 GetUserRankImage = GetUserRankImage ,
2007- GetClanImage = GetClanImage
2135+ GetClanImage = GetClanImage ,
2136+ GetPlayerNote = GetPlayerNote ,
2137+ SetPlayerNote = SetPlayerNote
20082138}
20092139
20102140function userHandler .SetTooltipBattle (battle )
0 commit comments