--[[ Combi Ring: team racing by fickleheart SRB2MB thread: http://mb.srb2.org/showthread.php?t=44095 While you're here, some tips: - The custom buttons operate turn signals! - Custom 1: Signal left - Custom 2: Signal right - Custom 3 OR Custom 1+2 together: Signal center Use them on path splits, or if you want to take a shortcut! It's probably curteous to respond to your teammate's signal by pressing the same, as well. - The ideal team is similar speed class, opposite weight class. The light racer should be steering HARD into everything and anchoring the heavy racer while they drift wide for huge miniturbos. Taking opposite sides also lets you pull each other back in if one of you goes off the track! - If one of you hits a fake, the other can use grow or invincibility to nullify the explosion's effect. If neither of you have that, the one with the fake should blow up behind the other so they can be carried forward and mitigate the time loss. - Don't take difficult shortcuts if you don't trust your teammate to handle them, or you'll lose a lot of time. - ALL boost types are shared between racers. Grow and invincibility too. - Stagger miniturbos so that they overlap as little as possible for maximum gain from each turn. - Don't use sneakers at the same time or you'll waste one! - Combine grow and invincibility for a fun time. - Remember to always own up to your mistakes, and congratulate your partner on what they do well! - It's just a game, and a wacky one at that. Getting a bad finish is okay because it usually happened because of something funny. - If you don't like being tethered to someone else in a server hosting this, you can choose to race by yourself by pressing alt+F4 and joining another server! - im gay ]] freeslot("MT_COMBILINK", "S_COMBILINK") mobjinfo[MT_COMBILINK] = { spawnstate = S_COMBILINK, flags = MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY } states[S_COMBILINK] = {SPR_THOK, 0} local cv_combi = CV_RegisterVar({"combi_active", "On", CV_NETVAR, CV_OnOff}) local cv_combifriends = CV_RegisterVar({"combi_allowfriends", "On", CV_NETVAR, CV_OnOff}) local cv_combireplay = CV_RegisterVar({"combi_replay", "Off", 0, { Off = 0, On = 1, Partial = 2, }}) -- A hack way to store data in replays local cv_combifriend = {} local MAXPLAYERS = #players for i = 0, MAXPLAYERS-1 do cv_combifriend[i] = CV_RegisterVar({"storage__combi_friend_" .. i, "0", CV_NETVAR, { MIN = 0, MAX = MAXPLAYERS, }}) end local combi_on = true local combi_initscroll = 0 addHook("NetVars", function(sync) --print("--- NETVARS ---") combi_on = sync(combi_on) combi_initscroll = sync(combi_initscroll) --print(combi_on and "combi on" or "combi off") --print("initscroll: " .. combi_initscroll) end) local START_TIME = 5*TICRATE + 20 local BASE_DISTANCE = 450 local ring_bits = {} local ring_bits_count = 0 local function PlaceRingBit(x, y, z, color) ring_bits_count = $+1 if ring_bits[ring_bits_count] and ring_bits[ring_bits_count].valid then P_TeleportMove(ring_bits[ring_bits_count], x, y, z) else ring_bits[ring_bits_count] = P_SpawnMobj(x, y, z, MT_COMBILINK) ring_bits[ring_bits_count].scale = (mapheaderinfo[gamemap] and mapheaderinfo[gamemap].mobj_scale or FRACUNIT)/4 end local bit = ring_bits[ring_bits_count] bit.fuse = 1 bit.color = color end addHook("ThinkFrame", do --print(" --- leveltime " .. leveltime .. " ---") if not (leveltime & 255) then pcall(do COM_BufInsertText(server, "karteliminatelast off") end) for i = 0, MAXPLAYERS-1 do local friend = 0 if players[i] and players[i].valid then local p = players[i] if not (p.combi_pending_friend and players[p.combi_pending_friend-1] and players[p.combi_pending_friend-1].valid) then p.combi_pending_friend = 0 end friend = p.combi_pending_friend end if cv_combifriend[i].value ~= friend then COM_BufInsertText(server, "storage__combi_friend_" .. i .. " " .. friend) --print(i .. "'s combi friend is updated to " .. friend) end end end local mapscale = (mapheaderinfo[gamemap] and mapheaderinfo[gamemap].mobj_scale or FRACUNIT) local MAX_DISTANCE = BASE_DISTANCE * mapscale ring_bits_count = 0 if leveltime == 0 then --print("reset goes here") combi_on = cv_combi.value combi_initscroll = -2*TICRATE for player in players.iterate do player.combi = nil player.combi_friend = cv_combifriends.value and cv_combifriend[#player].value or 0 --print(player.name .. "'s combi friend is " .. player.combi_friend) end elseif not combi_on then return elseif leveltime <= START_TIME then if leveltime == START_TIME then S_StartSound(nil, sfx_token) end combi_initscroll = $ - min((START_TIME - leveltime)*2/5, TICRATE*2) if combi_initscroll < 0 then combi_initscroll = $ + TICRATE*6 else return end local allPlayers = {} for player in players.iterate do if player.mo and player.mo.valid then player.old_combi = player.combi if player.combi_friend and players[player.combi_friend-1].valid and players[player.combi_friend-1].mo and players[player.combi_friend-1].mo.valid and players[player.combi_friend-1].combi_friend and players[player.combi_friend-1].combi_friend-1 == #player then player.combi = players[player.combi_friend-1] else table.insert(allPlayers, player) end end end while #allPlayers >= 2 do local one, two one = table.remove(allPlayers, P_RandomKey(#allPlayers)+1) two = table.remove(allPlayers, P_RandomKey(#allPlayers)+1) one.combi = two two.combi = one end if #allPlayers then allPlayers[1].combi = {valid = "maybe", name = "???"} end S_StartSound(nil, sfx_s1ba) end if leveltime < START_TIME then return end combi_initscroll = $/2 for player in players.iterate do if not (player.mo and player.mo.valid) then continue end -- Make tugs at the start of the race less crazy if leveltime < START_TIME + TICRATE then player.mo.momx = $/3 player.mo.momy = $/3 end -- Turn signal player.turn_buttons = $ or 0 local turn_buttons = player.cmd.buttons & (BT_CUSTOM1|BT_CUSTOM2|BT_CUSTOM3) if turn_buttons == BT_CUSTOM1 and not (player.turn_buttons & BT_CUSTOM1) then player.turn_signal = -3*TICRATE elseif turn_buttons == BT_CUSTOM2 and not (player.turn_buttons & BT_CUSTOM2) then player.turn_signal = 3*TICRATE elseif turn_buttons == BT_CUSTOM3 and player.turn_buttons ~= BT_CUSTOM3 then player.turn_signal = 3*TICRATE + FRACUNIT elseif turn_buttons == BT_CUSTOM1|BT_CUSTOM2 and player.turn_buttons ~= BT_CUSTOM1|BT_CUSTOM2 then player.turn_signal = 3*TICRATE + FRACUNIT end player.turn_buttons = turn_buttons if player.turn_signal then player.turn_signal = $ - (player.turn_signal > 0 and 1 or -1) if player.turn_signal == FRACUNIT then player.turn_signal = 0 end end -- combi stuff if not (player.combi and player.combi.valid and player.combi.valid ~= "maybe" and not player.combi.spectator) then player.combi = { valid = "uwu", name = "anti-loneliness \n gargoyle", mo = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z + 192*mapscale, MT_GARGOYLE), } --P_InstaThrust(player.combi.mo, player.mo.angle, -8*mapscale) player.combi.mo.flags = $ & ~MF_PUSHABLE end if player.combi.valid == "uwu" and leveltime == START_TIME+22 then player.kartstuff[k_squishedtimer] = TICRATE player.state = S_KART_SQUISH P_InstaThrust(player.combi.mo, player.mo.angle, -5*mapscale) player.combi.mo.momz = 4*mapscale end --print(player.name .. " <3 " .. player.combi.name) if not (player.mo and player.mo.valid and player.combi.mo and player.combi.mo.valid) then continue end for i = (player.combi.valid == "uwu" and 1 or 4), 6 do local m = i*FRACUNIT/7 PlaceRingBit( player.mo.x + FixedMul(player.combi.mo.x - player.mo.x, m), player.mo.y + FixedMul(player.combi.mo.y - player.mo.y, m), player.mo.z + FixedMul(player.combi.mo.z - player.mo.z, m) + 18*FRACUNIT, player.mo.color ) end local your_pull_xy = FRACUNIT local friend_pull_xy = FRACUNIT local your_pull_z = FRACUNIT local friend_pull_z = FRACUNIT if player.cmd.buttons & BT_ACCELERATE then your_pull_xy = $+FRACUNIT/2 end if player.cmd.buttons & BT_BRAKE then your_pull_xy = $+FRACUNIT end if player.mo.z > player.mo.floorz then if player.combi.valid == "uwu" then friend_pull_z = 0 your_pull_xy = $+FRACUNIT*2 else your_pull_z = $+FRACUNIT*2 end elseif player.combi.mo.z <= player.combi.mo.floorz and player.mo.z + 200*mapscale > player.combi.mo.z then -- If both players are on the ground and not too far apart on the Z axis, do no vertical pulling at all. -- This should make driving along ramps (esp Volcanic Valley) more tolerable. friend_pull_z = 0 end if player.combi.valid ~= "uwu" then if player.combi.cmd.buttons & BT_ACCELERATE then friend_pull_xy = $+FRACUNIT/2 end if player.combi.cmd.buttons & BT_BRAKE then friend_pull_xy = $+FRACUNIT end if player.combi.mo.z > player.combi.mo.floorz and friend_pull_z then friend_pull_z = $+FRACUNIT*2 end -- sync some state-related stuff while we're here if player.kartstuff[k_growshrinktimer] < -2 and player.combi.kartstuff[k_growshrinktimer] >= -2 then player.kartstuff[k_growshrinktimer] = -2 elseif player.kartstuff[k_growshrinktimer] >= 0 then player.kartstuff[k_growshrinktimer] = max($, player.combi.kartstuff[k_growshrinktimer]) end player.mo.destscale = max($, player.combi.mo.destscale) player.mo.scalespeed = max($, player.combi.mo.scalespeed) for _,prop in ipairs({k_sneakertimer, k_invincibilitytimer, k_driftboost, k_startboost}) do player.kartstuff[prop] = max($, player.combi.kartstuff[prop]) end player.realtime = min($, player.combi.realtime) if player.combi.exiting and not player.exiting then player.exiting = player.combi.exiting P_RestoreMusic(player) end end local yank_xy = FixedDiv(friend_pull_xy, your_pull_xy+friend_pull_xy)/4 local yank_z = FixedDiv(friend_pull_z, your_pull_xy+friend_pull_z)/4 if player.kartstuff[k_respawn] then if (not player.combi_respawn) or (player.mo.momz > -3*mapscale and player.mo.z - player.mo.floorz > 20*mapscale) then local dist = FixedMul(mapscale, 60) -- Let players shift sideways while respawning if they hold drift? if player.cmd.buttons & BT_DRIFT then local speed = player.cmd.driftturn/100 P_TryMove(player.mo, player.mo.x - sin(player.mo.angle)*speed, player.mo.y + cos(player.mo.angle)*speed, true) end player.combi_respawn = true --[[ this may have syncing issues P_TeleportMove(player.combi.mo, player.mo.x + dist*sin(player.mo.angle), player.mo.y - dist*cos(player.mo.angle), player.mo.z) player.combi.mo.momx, player.combi.mo.momy, player.combi.mo.momz = 0, 0, 0 player.combi.mo.angle = player.mo.angle ]] -- a3 variant for diagnostic purposes P_TeleportMove(player.combi.mo, player.mo.x, player.mo.y, player.mo.z + 100*mapscale) player.combi.mo.momx, player.combi.mo.momy, player.combi.mo.momz = 0, 0, 0 player.combi.mo.angle = player.mo.angle player.kartstuff[k_hyudorotimer] = max($, 2*TICRATE) if player.combi.valid ~= "uwu" then player.combi.kartstuff[k_hyudorotimer] = max($, 2*TICRATE) end --P_InstaThrust(player.combi.mo, player.mo.angle, -9*FRACUNIT) if player.combi.kartstuff then player.combi.kartstuff[k_respawn] = 0 end continue end else player.combi_respawn = false end local distance = P_AproxDistance(P_AproxDistance(player.mo.x - player.combi.mo.x, player.mo.y - player.combi.mo.y), player.mo.z - player.combi.mo.z) if distance < MAX_DISTANCE then continue end distance = $-MAX_DISTANCE local angle = R_PointToAngle2(player.mo.x, player.mo.y, player.combi.mo.x, player.combi.mo.y) local v_angle = R_PointToAngle2(0, player.mo.z, R_PointToDist2(player.mo.x, player.mo.y, player.combi.mo.x, player.combi.mo.y), player.combi.mo.z) P_Thrust(player.mo, angle, FixedMul(FixedMul(cos(v_angle), yank_xy), distance)) player.mo.momz = $+FixedMul(FixedMul(sin(v_angle), yank_z), distance) if player.combi.valid == "uwu" then P_Thrust(player.combi.mo, angle, -FixedMul(FixedMul(cos(v_angle), FRACUNIT-yank_xy), distance)) player.combi.mo.momz = $-FixedMul(FixedMul(sin(v_angle), FRACUNIT-yank_z), distance) end end end) -- Make rankings display per pair instead of per individual? addHook("ThinkFrame", do local individual_rank = {} --print("---------------") for player in players.iterate do player.combi_rank = nil if not player.spectator then --print(player.real_rank) player.real_rank = $ or player.kartstuff[k_position] local spot = 1 while individual_rank[spot] and individual_rank[spot].real_rank <= player.real_rank do spot = $+1 end table.insert(individual_rank, spot, player) end end local rank = 1 local team_rank = 0 while individual_rank[rank] do local player = individual_rank[rank] player.real_rank = nil if player.combi and player.combi.valid and player.combi.combi_rank then player.combi_rank = player.combi.combi_rank else team_rank = $+1 player.combi_rank = team_rank end if player.combi_rank ~= player.old_combi_rank then player.combi_rank_cooldown = 10 player.old_combi_rank = player.combi_rank elseif player.combi_rank_cooldown > 0 then player.combi_rank_cooldown = $-1 end -- This handles HUD display of the ranking, I think! player.kartstuff[k_position] = player.combi_rank player.kartstuff[k_positiondelay] = player.combi_rank_cooldown rank = $+1 end end) -- Force rankings to be mostly-correct on player thinkers. This causes the distance-checking code for giving SPBs to work properly. addHook("MobjThinker", do --print("-----") for player in players.iterate do -- This "real_rank" juggling is necessary to make sure we don't break the team position display. if not player.real_rank then player.real_rank = player.kartstuff[k_position] end if player.combi_rank then player.kartstuff[k_position] = player.combi_rank end end end, MT_PLAYER) -- HUD stuff local patches local ITEMX, ITEMY = 42, -3 local function DrawTeammateKartItem(teammate, v) if not patches then patches = { itembg = v.cachePatch("K_ISBG"), itembg2 = v.cachePatch("K_ISBGD"), itemtimer = v.cachePatch("K_ISIMER"), mulsticker = v.cachePatch("K_ISMUL"), sneaker = v.cachePatch("K_ISSHOE"), rocketsneaker = v.cachePatch("K_ISRSHE"), invincibility = { v.cachePatch("K_ISINV1"), v.cachePatch("K_ISINV2"), v.cachePatch("K_ISINV3"), v.cachePatch("K_ISINV4"), v.cachePatch("K_ISINV5"), v.cachePatch("K_ISINV6"), }, banana = v.cachePatch("K_ISBANA"), eggman = v.cachePatch("K_ISEGGM"), orbinaut = v.cachePatch("K_ISORBN"), jawz = v.cachePatch("K_ISJAWZ"), mine = v.cachePatch("K_ISMINE"), ballhog = v.cachePatch("K_ISBHOG"), spb = v.cachePatch("K_ISSPB"), grow = v.cachePatch("K_ISGROW"), shrink = v.cachePatch("K_ISSHRK"), shield = v.cachePatch("K_ISTHNS"), ghost = v.cachePatch("K_ISHYUD"), pogo = v.cachePatch("K_ISPOGO"), sink = v.cachePatch("K_ISSINK"), sad = v.cachePatch("K_ISSAD"), } end local patch = nil local bg = patches.itembg local inv = patches.invincibility[(leveltime/3) % 6 + 1] -- @TODO splitscreen local numberdisplaymin = 2 local itembar = 0 if teammate.kartstuff[k_itemroulette] then patch = ({ patches.sneaker, patches.banana, patches.orbinaut, patches.mine, patches.grow, patches.ghost, patches.rocketsneaker, patches.jawz, patches.spb, patches.shrink, inv, patches.eggman, patches.ballhog, patches.shield, })[(teammate.kartstuff[k_itemroulette]/3) % 13 + 1] elseif teammate.kartstuff[k_stolentimer] > 0 then patch = (leveltime & 2) and patches.ghost or nil elseif teammate.kartstuff[k_stealingtimer] > 0 and (leveltime & 2) then patch = patches.ghost elseif teammate.kartstuff[k_eggmanexplode] > 1 then patch = (leveltime & 1) and patches.eggman or nil elseif teammate.kartstuff[k_rocketsneakertimer] > 1 then itembar = teammate.kartstuff[k_rocketsneakertimer] patch = (leveltime & 1) and patches.rocketsneaker or nil elseif teammate.kartstuff[k_growshrinktimer] > 1 then patch = (leveltime & 1) and patches.grow or nil elseif teammate.kartstuff[k_sadtimer] > 0 then patch = (leveltime & 2) and patches.sad or nil elseif teammate.kartstuff[k_itemamount] == 0 then return elseif (not teammate.kartstuff[k_itemheld]) or (leveltime & 1) then patch = ({ [KITEM_SNEAKER] = patches.sneaker, [KITEM_ROCKETSNEAKER] = patches.rocketsneaker, [KITEM_INVINCIBILITY] = inv, [KITEM_BANANA] = patches.banana, [KITEM_EGGMAN] = patches.eggman, [KITEM_ORBINAUT] = patches.orbinaut, [KITEM_JAWZ] = patches.jawz, [KITEM_MINE] = patches.mine, [KITEM_BALLHOG] = patches.ballhog, [KITEM_SPB] = patches.spb, [KITEM_GROW] = patches.grow, [KITEM_SHRINK] = patches.shrink, [KITEM_THUNDERSHIELD] = patches.shield, [KITEM_HYUDORO] = patches.ghost, [KITEM_POGOSPRING] = patches.pogo, [KITEM_KITCHENSINK] = patches.sink, [KITEM_SAD] = patches.sad, })[teammate.kartstuff[k_itemtype]] if patch == inv then bg = patches.itembg2 end end local flags = V_HUDTRANS|V_SNAPTOTOP|V_SNAPTOLEFT v.draw(ITEMX, ITEMY, bg, flags) if teammate.kartstuff[k_itemamount] >= numberdisplaymin and not teammate.kartstuff[k_itemroulette] then v.draw(ITEMX, ITEMY, patches.mulsticker, flags) if patch then v.draw(ITEMX, ITEMY, patch, flags) end v.drawString(ITEMX+24, ITEMY+31, "x" .. teammate.kartstuff[k_itemamount], V_ALLOWLOWERCASE|flags) elseif patch then v.draw(ITEMX, ITEMY, patch, flags) end if itembar then local itemtime = 8*TICRATE local barlength = 12 local maxl = (itemtime*3) - barlength local fill = (itembar*barlength)/maxl local length = min(barlength, fill) local height = 1 v.draw(ITEMX+17, ITEMY+27, patches.itemtimer, flags) v.drawFill(ITEMX+18, ITEMY+18, (length == 2 and 2 or 1), height, 12|flags) if length > 2 then v.drawFill(ITEMX+17+length, ITEMY+28, 1, height, 12|flags) v.drawFill(ITEMX+19, ITEMY+28, length-2, 1, 120|flags) end end -- draw the teammate's face too! if teammate.mo and teammate.mo.valid then v.draw(ITEMX+30, ITEMY+12, v.cachePatch(skins[teammate.mo.skin].facemmap), flags, v.getColormap(teammate.mo.skin, teammate.mo.color)) end end hud.add(function(v, player) if player.combi and player.combi.valid then -- @TODO splitscreen local scroll = FixedMul(FixedDiv(combi_initscroll, 6*TICRATE), 10) local pos = min(max(leveltime - 100, 120), 160) local flags = V_SNAPTOBOTTOM | (leveltime > START_TIME+TICRATE and V_HUDTRANS or 0) if leveltime <= START_TIME then v.drawFill(0, pos - 3, 320, 14, 24|V_SNAPTOBOTTOM) end v.drawString(150, pos, player.name, V_ALLOWLOWERCASE|flags, "right") v.drawString(170, pos - scroll, player.combi.name, V_ALLOWLOWERCASE|flags, "left") if player.old_combi and player.old_combi.valid and scroll > 0 then v.drawString(170, pos + 10 - scroll, player.old_combi.name, V_ALLOWLOWERCASE|flags, "left") end v.drawFill(153, pos-1, 14, 6, 31|flags) v.drawFill(154, pos-2, 12, 2, 31|flags) v.drawFill(154, pos+5, 12, 1, 31|flags) v.drawFill(155, pos+6, 10, 1, 31|flags) v.drawFill(156, pos+7, 8, 1, 31|flags) v.drawFill(157, pos+8, 6, 1, 31|flags) v.drawFill(158, pos+9, 4, 1, 31|flags) v.drawFill(154, pos, 12, 4, 128|flags) v.drawFill(155, pos-1, 4, 1, 128|flags) v.drawFill(161, pos-1, 4, 1, 128|flags) v.drawFill(155, pos+4, 10, 1, 128|flags) v.drawFill(156, pos+5, 8, 1, 128|flags) v.drawFill(157, pos+6, 6, 1, 128|flags) v.drawFill(158, pos+7, 4, 1, 128|flags) v.drawFill(159, pos+8, 2, 1, 128|flags) if leveltime <= START_TIME then local color = min(leveltime & 31, 31 - (leveltime & 31)) + 160 v.drawFill(0, pos - 9, 320, 6, color|V_SNAPTOBOTTOM) v.drawFill(0, pos + 11, 320, 6, color|V_SNAPTOBOTTOM) end if player.combi.valid ~= "uwu" and player.combi.valid ~= "maybe" then DrawTeammateKartItem(player.combi, v) end local turn_left, turn_right = v.cachePatch("MARRD0"), v.cachePatch("MARRA0") if player.turn_signal then local trans = abs(player.turn_signal / 5) % 3 + 2 trans = $ << FF_TRANSSHIFT if player.turn_signal < 0 then v.drawScaled(150*FRACUNIT, 198*FRACUNIT, FRACUNIT/8, turn_left, trans) elseif player.turn_signal > FRACUNIT then v.drawScaled(154*FRACUNIT, 198*FRACUNIT, FRACUNIT/8, turn_right, trans) v.drawScaled(168*FRACUNIT, 198*FRACUNIT, FRACUNIT/8, turn_left, trans) else v.drawScaled(170*FRACUNIT, 198*FRACUNIT, FRACUNIT/8, turn_right, trans) end end if player.combi.turn_signal then local trans = abs(player.combi.turn_signal / 5) % 3 + 6 trans = $ << FF_TRANSSHIFT if player.combi.turn_signal < 0 then v.drawScaled(80*FRACUNIT, 160*FRACUNIT, FRACUNIT, turn_left, trans) elseif player.combi.turn_signal > FRACUNIT then v.drawScaled(128*FRACUNIT, 96*FRACUNIT, FRACUNIT/2, turn_right, trans) v.drawScaled(192*FRACUNIT, 96*FRACUNIT, FRACUNIT/2, turn_left, trans) else v.drawScaled(240*FRACUNIT, 160*FRACUNIT, FRACUNIT, turn_right, trans) end end end end) -- Fix people getting stuck in the ALG sometimes local function unstick(me, other) if not combi_on then return end if me.partnered and me.z < other.z+other.height and other.z < me.z+me.height then me.momz = $+FRACUNIT end end addHook("MobjCollide", unstick, MT_GARGOYLE) addHook("MobjMoveCollide", unstick, MT_GARGOYLE) -- Friend system! local function PRINTF(p,m) CONS_Printf(p,m) end -- discard the third argument COM_AddCommand("combi_friend", function(player, friend, in_chat) local printfunc = in_chat and chatprintf or PRINTF local color = in_chat and "\x84" or "" if not cv_combi.value then if not in_chat then printfunc(player, color .. "Combi isn't on right now.", false) end return end if not cv_combifriends.value then printfunc(player, color .. "The server has disabled combi friends. :(", false) return end if friend == "none" then if player.combi_pending_friend and players[player.combi_pending_friend-1].valid then chatprintf(players[player.combi_pending_friend-1], "\x84" .. player.name .. " is no longer your combi friend. :(", true) end player.combi_pending_friend = 0 COM_BufInsertText(server, "storage__combi_friend_" .. #player .. " 0") printfunc(player, color .. "You no longer have a combi friend. :(", false) return end if friend == player.name then printfunc(player, color .. "You can't befriend yourself!", false) return end local friend_player for p in players.iterate do if p.name == friend then friend_player = p break end end if not friend_player then if not in_chat then CONS_Printf(player, "combi_friend \"\": Pick someone to always be your combi partner! They must return the favor.") end return end player.combi_pending_friend = #friend_player+1 COM_BufInsertText(server, "storage__combi_friend_" .. #player .. " " .. (#friend_player+1)) local str = friend .. " will be your friend if they accept." if friend_player.combi_pending_friend and friend_player.combi_pending_friend-1 == #player then str = friend .. " will always be your combi partner." end printfunc(player, color .. str, false) str = player.name .. " wants to be combi friends. Say \"friend " .. player.name .. "\" to accept." if friend_player.combi_pending_friend and friend_player.combi_pending_friend-1 == #player then str = player.name .. " accepted your friend request, and will always be your combi partner." end chatprintf(friend_player, "\x84" .. str, true) end) addHook("PlayerMsg", function(player, msgtype, target, message) if message:sub(1, 7) == "friend " then COM_BufInsertText(player, "combi_friend \"" .. message:sub(8) .. "\" in_chat") end end)