Help with player control plugin
#1
I'm currently porting Bukkit's iControlU plugin to Cuberite. This plugin allows a player to control another player's movement and inventory, and chat as the player.

The movement part of the plugin works just fine, but I'm having trouble with the inventory and chat. What I want to do is to send the inventory of the controller to the target whenever the inventories are different from each other, without causing a deadlock. The inventory is sent, but eventually (or sometimes immediately) the server stops responding. As for the chat, is there a way to chat as another player at the moment?

Info.lua:
g_PluginInfo = {
	Name = "iControlU",
	Version = "1",
	Date = "2017-07-12",
	SourceLocation = "https://github.com/mathiascode/iControlU",
	Description = [[Plugin for Cuberite that allows players to control other players.]],

	Commands =
	{
		["/icu"] =
		{
			HelpString = "Control another player's movement",
			Permission = "icu.command",
			Subcommands =
			{
				control =
				{
					Alias = "c",
					Permission = "icu.control",
					Handler = HandleControlCommand,
				},
				stop =
				{
					Alias = "s",
					Permission = "icu.stop",
					Handler = HandleStopCommand,
				},
			},
		},
	},
	Permissions =
	{
		["icu.exempt"] =
		{
			Description = "Players with this permission cannot be controlled",
			RecommendedGroups = "everyone",
		},
	},
}

main.lua:
ControllerFor = {}
TargetFor = {}
Inventory = {}
GlobalTime = 0

function Initialize(Plugin)
	Plugin:SetName(g_PluginInfo.Name)
	Plugin:SetVersion(g_PluginInfo.Version)

	cPluginManager:AddHook(cPluginManager.HOOK_CHAT, OnChat)
	cPluginManager:AddHook(cPluginManager.HOOK_ENTITY_CHANGING_WORLD, OnEntityChangingWorld)
	cPluginManager:AddHook(cPluginManager.HOOK_EXECUTE_COMMAND, OnExecuteCommand)
	cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_ANIMATION, OnPlayerAnimation)
	cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_DESTROYED, OnPlayerDestroyed)
	cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_LEFT_CLICK, OnPlayerLeftClick)
	cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_MOVING, OnPlayerMoving)
	cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_TOSSING_ITEM, OnPlayerTossingItem)
	cPluginManager:AddHook(cPluginManager.HOOK_TAKE_DAMAGE, OnTakeDamage)
	cPluginManager:AddHook(cPluginManager.HOOK_WORLD_TICK, OnWorldTick)

	dofile(cPluginManager:GetPluginsPath() .. "/InfoReg.lua")
	RegisterPluginInfoCommands()

	LOG("Initialised " .. Plugin:GetName() .. " v." .. Plugin:GetVersion())
	return true
end

	--for i=0,40 do
		--local Item = Inventory[Controller:GetUUID()]:GetSlot(i)
		--Controller:GetInventory():SetSlot(i, Item)
	--end
	--Inventory[Controller:GetUUID()] = nil

function HandleControlCommand(Split, Controller)
	if Split[3] == nil then
		Controller:SendMessageInfo("Usage: " .. Split[1] .. " " .. Split[2] .. " <player>")
	else
			local Control = function(Target)
				if Target == Controller then
					Controller:SendMessageFailure("You are already in control of yourself")
				elseif Target:HasPermission("icu.exempt") then
					Controller:SendMessageFailure("You can't control this player")
				else
					Controller:TeleportToEntity(Target)
					Inventory[Controller:GetUUID()] = Controller:GetInventory()
					for i=0,40 do
						Controller:GetInventory():SetSlot(i, Target:GetInventory():GetSlot(i))
					end
					ControllerFor[Target:GetUUID()] = Controller
					TargetFor[Controller:GetUUID()] = Target
					Controller:SendMessageSuccess("You are now controlling \"" .. Target:GetName() .. "\"")
				end
			end
			if Split[3] == "" or not cRoot:Get():FindAndDoWithPlayer(Split[3], Control) then
				Controller:SendMessageFailure("Player \"" .. Split[3] .. "\" not found")
				return true
			end
	end
	return true
end

function HandleStopCommand(Split, Controller)
	local Target = TargetFor[Controller:GetUUID()]
	if Target then
		ControllerFor[Target:GetUUID()] = nil
		TargetFor[Controller:GetUUID()] = nil
		Controller:GetWorld():ScheduleTask(200,
			function()
				Controller:SetVisible(true)
			end
		)
		Target:Unfreeze()
		Controller:SendMessageSuccess("You are no longer controlling \"" .. Target:GetName() .. "\". You are invisible for 10 seconds.")
	else
		Controller:SendMessageFailure("You are not controlling anyone at the moment")
	end
	return true
end

function OnChat(Target, Message)
	if ControllerFor[Target:GetUUID()] then
		Target:SendMessageInfo("You cannot chat while being controlled")
		return true
	end
end

function OnEntityChangingWorld(Controller, World)
        if Controller:IsPlayer() then
		local Target = TargetFor[Controller:GetUUID()]
		if Target then
			Target:MoveToWorld(Controller:GetWorld())
		end
        end
end

function OnExecuteCommand(Target, CommandSplit, EntireCommand)
	if Target and ControllerFor[Target:GetUUID()] then
		Target:SendMessageInfo("You cannot run commands while being controlled")
		return true
	end
end

function OnPlayerAnimation(Controller, Animation)
	local Target = TargetFor[Controller:GetUUID()]
	if Target then
		Controller:GetClientHandle():SendEntityAnimation(Target, Animation)
	end
end

function OnPlayerDestroyed(Player)
	cRoot:Get():ForEachPlayer(
		function(OtherPlayer)
			if ControllerFor[Player:GetUUID()] == OtherPlayer then
				ControllerFor[Player:GetUUID()] = nil
				TargetFor[OtherPlayer:GetUUID()] = nil
				OtherPlayer:GetWorld():ScheduleTask(200,
					function()
						OtherPlayer:SetVisible(true)
					end
				)
				OtherPlayer:SendMessageInfo("The player you were controlling has disconnected. You are invisible for 10 seconds.")
			end
			if TargetFor[Player:GetUUID()] == OtherPlayer then
				ControllerFor[OtherPlayer:GetUUID()] = nil
				TargetFor[Player:GetUUID()] = nil
				OtherPlayer:Unfreeze()
			end
		end
	)
end

function OnPlayerLeftClick(Target, BlockX, BlockY, BlockZ, BlockFace, Action)
	if ControllerFor[Target:GetUUID()] then
		return true
	end
end

function OnPlayerMoving(Target, OldPosition, NewPosition)
	if ControllerFor[Target:GetUUID()] then
		return true
	end
end

function OnPlayerRightClick(Target, BlockX, BlockY, BlockZ, BlockFace, CursorX, CursorY, CursorZ)
	if ControllerFor[Target:GetUUID()] then
		return true
	end
end

function OnPlayerTossingItem(Target)
	if ControllerFor[Target:GetUUID()] then
		return true
	end
end

function OnTakeDamage(Controller, TDI)
	if Controller:IsPlayer() then
		local Target = TargetFor[Controller:GetUUID()]
		if Target then
			Target:TakeDamage(TDI.DamageType, Controller, TDI.RawDamage, TDI.FinalDamage, TDI.Knockback)
		end
	end
end

function OnWorldTick(World, TimeDelta)
	cRoot:Get():ForEachPlayer(
		function(Target)
			local Controller = ControllerFor[Target:GetUUID()]
			if Controller then
				Target:TeleportToEntity(Controller)
				Target:SetSpeed(0,0,0)
				Target:Freeze(Target:GetPosition())
				Target:SendRotation(Controller:GetYaw(), Controller:GetPitch())
				Target:SetHeadYaw(Controller:GetHeadYaw())
				Target:SetYaw(Controller:GetYaw())
				Target:SetPitch(Controller:GetPitch())
				Target:SetIsFireproof(Controller:IsFireproof())
				Target:SetGameMode(Controller:GetGameMode())
				Target:SetCrouch(Controller:IsCrouched())
				Target:SetFlying(Controller:IsFlying())
				Target:SetFoodLevel(Controller:GetFoodLevel())
				Target:SetSprint(Controller:IsSprinting())
				Controller:SetVisible(false)
        			if GlobalTime == 20 then
					if Controller:GetInventory() ~= Target:GetInventory() then
						for i=0,40 do
							local Slot = Controller:GetInventory():GetSlot(i)
							Target:GetInventory():SetSlot(i, Slot)
						end
					end
                                else
					GlobalTime = GlobalTime + 1
				end
			end
		end
	)
end

function OnDisable()
        cRoot:Get():ForEachPlayer(
		function(Controller)
			local Target = TargetFor[Controller:GetUUID()]
			if Target then
				Target:Unfreeze()
				Controller:SetVisible(true)
				Controller:SendMessageInfo("You are no longer controlling a player due to server reload")
			end
		end
	)
	LOG("Disabled " .. cPluginManager:GetCurrentPlugin():GetName() .. "!")
end
Reply
Thanks given by:
#2
There already is a plugin to view a user's inventory: https://forum.cuberite.org/thread-2492.html

I'm not sure if it's possible to force a player to chat. You could try cPluginManager:ExecuteCommand on a player without a slash before the command string, but I seriously doubt it would work.
Reply
Thanks given by:
#3
I know, but I want to keep the inventories synced, without opening windows. Whenever the controller modifies his inventory, the inventory should be sent to the victim/target. If the victim modifies his inventory, the controller's inventory should be sent to them. Not sure what I'm doing wrong to cause deadlocks.
Reply
Thanks given by:
#4
We probably need a global OnSlotChanged event for that. Once that's available you can keep track of who has an inventory open of another player and update what they see accordingly.
Reply
Thanks given by:
#5
I've fixed this issue by using ForEachPlayer with cWorld instead of cRoot in the tick hook.
Reply
Thanks given by:




Users browsing this thread: 3 Guest(s)