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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
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: 1 Guest(s)