Skip to content

Commit c15f337

Browse files
committed
Merge PR #1898 by @flo - floating name tags
2 parents db2718a + 2f80e8f commit c15f337

File tree

12 files changed

+612
-24
lines changed

12 files changed

+612
-24
lines changed

engine/src/main/java/org/terasology/logic/characters/CharacterComponent.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
* @author Immortius
3434
*/
3535
public final class CharacterComponent implements Component {
36+
/**
37+
* Recommended height from center at which name tags should be placed if there is one.
38+
*/
39+
public float nameTagOffset = 0.8f;
3640
public float eyeOffset = 0.6f;
3741
/**
3842
* Specifies the maximium range at which this character is able to interact with other objects.

engine/src/main/java/org/terasology/logic/console/commands/ServerCommands.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.terasology.logic.console.commandSystem.annotations.Command;
2929
import org.terasology.logic.console.commandSystem.annotations.CommandParam;
3030
import org.terasology.logic.console.commandSystem.annotations.Sender;
31+
import org.terasology.logic.console.suggesters.UsernameSuggester;
3132
import org.terasology.logic.permission.PermissionManager;
3233
import org.terasology.logic.players.PlayerUtil;
3334
import org.terasology.math.geom.Vector3i;
@@ -101,6 +102,32 @@ public String kickUser(@CommandParam("username") String username) {
101102
throw new IllegalArgumentException("No such user '" + username + "'");
102103
}
103104

105+
@Command(shortDescription = "Rename a user", runOnServer = true,
106+
requiredPermission = PermissionManager.USER_MANAGEMENT_PERMISSION)
107+
public String renameUser(
108+
@CommandParam(value = "userName", suggester = UsernameSuggester.class) String userName,
109+
@CommandParam(value = "newUserName") String newUserName) {
110+
Iterable<EntityRef> clientInfoEntities = entityManager.getEntitiesWith(ClientInfoComponent.class);
111+
for (EntityRef clientInfo : clientInfoEntities) {
112+
DisplayNameComponent nameComp = clientInfo.getComponent(DisplayNameComponent.class);
113+
if (newUserName.equals(nameComp.name)) {
114+
throw new IllegalArgumentException("New user name is already in use");
115+
}
116+
}
117+
118+
119+
for (EntityRef clientInfo : clientInfoEntities) {
120+
DisplayNameComponent nameComp = clientInfo.getComponent(DisplayNameComponent.class);
121+
if (userName.equals(nameComp.name)) {
122+
nameComp.name = newUserName;
123+
clientInfo.saveComponent(nameComp);
124+
return "User " + userName + " has been renamed to " + newUserName;
125+
}
126+
}
127+
128+
throw new IllegalArgumentException("No such user '" + userName + "'");
129+
}
130+
104131
@Command(shortDescription = "Kick user by ID", runOnServer = true,
105132
requiredPermission = PermissionManager.USER_MANAGEMENT_PERMISSION)
106133
public String kickUserByID(@CommandParam("userId") int userId) {
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright 2014 MovingBlocks
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.terasology.logic.nameTags;
17+
18+
import org.slf4j.Logger;
19+
import org.slf4j.LoggerFactory;
20+
import org.terasology.entitySystem.entity.EntityBuilder;
21+
import org.terasology.entitySystem.entity.EntityManager;
22+
import org.terasology.entitySystem.entity.EntityRef;
23+
import org.terasology.entitySystem.entity.lifecycleEvents.BeforeDeactivateComponent;
24+
import org.terasology.entitySystem.entity.lifecycleEvents.OnActivatedComponent;
25+
import org.terasology.entitySystem.entity.lifecycleEvents.OnChangedComponent;
26+
import org.terasology.entitySystem.event.ReceiveEvent;
27+
import org.terasology.entitySystem.systems.BaseComponentSystem;
28+
import org.terasology.entitySystem.systems.RegisterMode;
29+
import org.terasology.entitySystem.systems.RegisterSystem;
30+
import org.terasology.logic.location.Location;
31+
import org.terasology.logic.location.LocationComponent;
32+
import org.terasology.math.geom.Quat4f;
33+
import org.terasology.math.geom.Vector3f;
34+
import org.terasology.registry.In;
35+
import org.terasology.rendering.logic.FloatingTextComponent;
36+
37+
import java.util.HashMap;
38+
import java.util.Map;
39+
40+
41+
@RegisterSystem(RegisterMode.CLIENT)
42+
public class NameTagClientSystem extends BaseComponentSystem {
43+
private static final Logger logger = LoggerFactory.getLogger(NameTagClientSystem.class);
44+
45+
private Map<EntityRef, EntityRef> nameTagEntityToFloatingTextMap = new HashMap<>();
46+
47+
@In
48+
private EntityManager entityManager;
49+
50+
51+
@ReceiveEvent(components = {NameTagComponent.class, LocationComponent.class})
52+
public void onNameTagOwnerActivated(OnActivatedComponent event, EntityRef entity,
53+
NameTagComponent nameTagComponent) {
54+
createOrUpdateNameTagFor(entity, nameTagComponent);
55+
}
56+
57+
@ReceiveEvent(components = {NameTagComponent.class })
58+
public void onDisplayNameChange(OnChangedComponent event, EntityRef entity,
59+
NameTagComponent nameTagComponent) {
60+
createOrUpdateNameTagFor(entity, nameTagComponent);
61+
}
62+
63+
64+
private void createOrUpdateNameTagFor(EntityRef entity, NameTagComponent nameTagComponent) {
65+
EntityRef nameTag = nameTagEntityToFloatingTextMap.get(entity);
66+
Vector3f offset = new Vector3f(0, nameTagComponent.yOffset, 0);
67+
if (nameTag != null) {
68+
FloatingTextComponent floatingText = nameTag.getComponent(FloatingTextComponent.class);
69+
floatingText.text = nameTagComponent.text;
70+
floatingText.textColor = nameTagComponent.textColor;
71+
nameTag.saveComponent(floatingText);
72+
LocationComponent nameTagLoc = nameTag.getComponent(LocationComponent.class);
73+
nameTagLoc.setLocalPosition(offset);
74+
nameTag.saveComponent(nameTagLoc);
75+
} else {
76+
EntityBuilder nameTagBuilder = entityManager.newBuilder();
77+
FloatingTextComponent floatingTextComponent = new FloatingTextComponent();
78+
nameTagBuilder.addComponent(floatingTextComponent);
79+
LocationComponent locationComponent = new LocationComponent();
80+
nameTagBuilder.addComponent(locationComponent);
81+
floatingTextComponent.text = nameTagComponent.text;
82+
floatingTextComponent.textColor = nameTagComponent.textColor;
83+
nameTagBuilder.setOwner(entity);
84+
nameTagBuilder.setPersistent(false);
85+
86+
nameTag = nameTagBuilder.build();
87+
nameTagEntityToFloatingTextMap.put(entity, nameTag);
88+
89+
Location.attachChild(entity, nameTag, offset, new Quat4f(1, 0, 0, 0));
90+
}
91+
}
92+
93+
private void destroyNameTagOf(EntityRef entity) {
94+
EntityRef nameTag = nameTagEntityToFloatingTextMap.remove(entity);
95+
if (nameTag != null) {
96+
nameTag.destroy();
97+
}
98+
}
99+
100+
101+
@ReceiveEvent(components = {NameTagComponent.class })
102+
public void onNameTagOwnerRemoved(BeforeDeactivateComponent event, EntityRef entity) {
103+
destroyNameTagOf(entity);
104+
}
105+
106+
@Override
107+
public void shutdown() {
108+
/* Explicitly no deletion of name tag entities as some system might not be in the right state anymore.
109+
* Since they aren't persistent it does not make any difference anyway.
110+
*/
111+
nameTagEntityToFloatingTextMap.clear();
112+
}
113+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2014 MovingBlocks
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.terasology.logic.nameTags;
17+
18+
import org.terasology.entitySystem.Component;
19+
import org.terasology.rendering.nui.Color;
20+
21+
/**
22+
* Will make the entity have a name tag overhead in the 3D view.
23+
*
24+
* The text on name tag is based on the {@link org.terasology.logic.common.DisplayNameComponent} this entity.
25+
*
26+
* The color of the name tag is based on the {@link org.terasology.network.ColorComponent} of this entity
27+
*/
28+
public class NameTagComponent implements Component {
29+
30+
public float yOffset = 0.3f;
31+
32+
public String text;
33+
34+
public Color textColor = Color.WHITE;
35+
36+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
* Copyright 2015 MovingBlocks
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.terasology.logic.nameTags;
17+
18+
import org.slf4j.Logger;
19+
import org.slf4j.LoggerFactory;
20+
import org.terasology.entitySystem.entity.EntityRef;
21+
import org.terasology.entitySystem.entity.lifecycleEvents.OnActivatedComponent;
22+
import org.terasology.entitySystem.entity.lifecycleEvents.OnChangedComponent;
23+
import org.terasology.entitySystem.event.ReceiveEvent;
24+
import org.terasology.entitySystem.systems.BaseComponentSystem;
25+
import org.terasology.entitySystem.systems.RegisterMode;
26+
import org.terasology.entitySystem.systems.RegisterSystem;
27+
import org.terasology.logic.characters.CharacterComponent;
28+
import org.terasology.logic.common.DisplayNameComponent;
29+
import org.terasology.logic.players.event.OnPlayerSpawnedEvent;
30+
import org.terasology.network.ClientComponent;
31+
import org.terasology.network.ClientInfoComponent;
32+
import org.terasology.network.ColorComponent;
33+
import org.terasology.network.NetworkSystem;
34+
import org.terasology.registry.In;
35+
import org.terasology.rendering.nui.Color;
36+
37+
38+
/**
39+
* Creates name tags for chacters controlled by players based on their name.
40+
*
41+
* Once there is the intention to implement multiple differnt name tag generation rules for players feel free to move
42+
* this system into a module so that it isn't always enabled.
43+
*/
44+
@RegisterSystem(RegisterMode.CLIENT)
45+
public class PlayerNameTagSystem extends BaseComponentSystem {
46+
private static final Logger logger = LoggerFactory.getLogger(NameTagClientSystem.class);
47+
48+
@In
49+
private NetworkSystem networkSystem;
50+
51+
/**
52+
* Listening for {@link OnPlayerSpawnedEvent} does not work, as it is an authority event that does not get
53+
* processed at clients. That is why we listen for the activation.
54+
*/
55+
@ReceiveEvent(components = CharacterComponent.class)
56+
public void onCharacterActivation(OnActivatedComponent event, EntityRef characterEntity,
57+
CharacterComponent characterComponent) {
58+
EntityRef ownerEntity = networkSystem.getOwnerEntity(characterEntity);
59+
if (ownerEntity == null) {
60+
return; // NPC
61+
}
62+
63+
ClientComponent clientComponent = ownerEntity.getComponent(ClientComponent.class);
64+
if (clientComponent == null) {
65+
logger.warn("Can't create player based name tag for character as owner has no client component");
66+
return;
67+
}
68+
if (clientComponent.local) {
69+
return; // the character belongs to the local player and does not need a name tag
70+
}
71+
72+
EntityRef clientInfoEntity = clientComponent.clientInfo;
73+
74+
DisplayNameComponent displayNameComponent = clientInfoEntity.getComponent(DisplayNameComponent.class);
75+
if (displayNameComponent == null) {
76+
logger.error("Can't create player based name tag for character as client info has no DisplayNameComponent");
77+
return;
78+
}
79+
String name = displayNameComponent.name;
80+
81+
float yOffset = characterComponent.nameTagOffset;
82+
83+
Color color = Color.WHITE;
84+
ColorComponent colorComponent = clientInfoEntity.getComponent(ColorComponent.class);
85+
if (colorComponent != null) {
86+
color = colorComponent.color;
87+
}
88+
89+
NameTagComponent nameTagComponent = characterEntity.getComponent(NameTagComponent.class);
90+
boolean newComponent = nameTagComponent == null;
91+
if (nameTagComponent == null) {
92+
nameTagComponent = new NameTagComponent();
93+
}
94+
nameTagComponent.text = name;
95+
nameTagComponent.textColor = color;
96+
nameTagComponent.yOffset = yOffset;
97+
if (newComponent) {
98+
characterEntity.addComponent(nameTagComponent);
99+
} else {
100+
characterEntity.saveComponent(nameTagComponent);
101+
}
102+
103+
}
104+
105+
/**
106+
* The player entity may currently become "local" afterh the player has been activated.
107+
*
108+
* To address this issue the name tag component will be removed again when a client turns out to be local
109+
* afterwards.
110+
*/
111+
@ReceiveEvent
112+
public void onClientComponentChange(OnChangedComponent event, EntityRef clientEntity,
113+
ClientComponent clientComponent) {
114+
if (clientComponent.local) {
115+
EntityRef character = clientComponent.character;
116+
if (character.exists() && character.hasComponent(NameTagComponent.class)) {
117+
character.removeComponent(NameTagComponent.class);
118+
}
119+
}
120+
}
121+
122+
@ReceiveEvent
123+
public void onDisplayNameChange(OnChangedComponent event, EntityRef clientInfoEntity,
124+
DisplayNameComponent displayNameComponent) {
125+
ClientInfoComponent clientInfoComp = clientInfoEntity.getComponent(ClientInfoComponent.class);
126+
if (clientInfoComp == null) {
127+
return; // not a client info object
128+
}
129+
130+
EntityRef clientEntity = clientInfoComp.client;
131+
if (!clientEntity.exists()) {
132+
return; // offline players aren't visible: nothing to do
133+
}
134+
135+
ClientComponent clientComponent = clientEntity.getComponent(ClientComponent.class);
136+
if (clientComponent == null) {
137+
logger.warn("Can't update name tag as client entity lacks ClietnComponent");
138+
return;
139+
}
140+
141+
EntityRef characterEntity = clientComponent.character;
142+
if (characterEntity == null || !characterEntity.exists()) {
143+
return; // player has no character, nothing to do
144+
}
145+
146+
NameTagComponent nameTagComponent = characterEntity.getComponent(NameTagComponent.class);
147+
if (nameTagComponent == null) {
148+
return; // local players don't have a name tag
149+
}
150+
151+
nameTagComponent.text = displayNameComponent.name;
152+
characterEntity.saveComponent(nameTagComponent);
153+
}
154+
}

0 commit comments

Comments
 (0)