add support for throwing objects, add snowball mob

This commit is contained in:
darkrose 2014-11-22 01:31:18 +10:00
parent 0523c7c905
commit 0460fa439b
10 changed files with 382 additions and 230 deletions

View File

@ -1591,6 +1591,39 @@ void Client::groundAction(u8 action, v3s16 nodepos_undersurface,
}
}
void Client::throwItem(v3f dir, u16 item)
{
std::ostringstream os(std::ios_base::binary);
u8 buf[15];
LocalPlayer* player = m_env.getLocalPlayer();
// Write command
writeU16(buf, TOSERVER_THROWITEM);
os.write((char*)buf, 2);
// Write position
v3f pf = player->getEyePosition();
v3s32 position(pf.X*100, pf.Y*100, pf.Z*100);
writeV3S32(buf,position);
os.write((char*)buf, 12);
// Write speed/direction
v3s32 dir_i(dir.X*100, dir.Y*100, dir.Z*100);
writeV3S32(buf,dir_i);
os.write((char*)buf, 12);
// Write item
writeU16(buf, item);
os.write((char*)buf, 2);
// Make data buffer
std::string s = os.str();
SharedBuffer<u8> data((u8*)s.c_str(), s.size());
// Send as reliable
Send(0, data, true);
}
void Client::clickActiveObject(u8 button, u16 id, u16 item_i)
{
if (connectedAndInitialized() == false) {

View File

@ -207,6 +207,7 @@ public:
void groundAction(u8 action, v3s16 nodepos_undersurface,
v3s16 nodepos_oversurface, u16 item);
void throwItem(v3f dir, u16 item);
void clickActiveObject(u8 button, u16 id, u16 item_i);
void sendNodemetaFields(v3s16 p, const std::string &formname,

View File

@ -251,6 +251,14 @@ enum ToServerCommand
[0] u16 TOSERVER_INIT2
*/
TOSERVER_THROWITEM = 0x12,
/*
[0] u16 command
[2] v3s32 position*100
[14] v3s32 speed*100
[26] u16 item
*/
TOSERVER_GETBLOCK=0x20, // Obsolete
TOSERVER_ADDNODE = 0x21, // Obsolete
TOSERVER_REMOVENODE = 0x22, // Obsolete

View File

@ -375,6 +375,7 @@ void content_craftitem_init()
f->texture = "snow_ball.png";
f->name = "snow_ball";
f->description = wgettext("Snow Ball");
f->thrown_item = CONTENT_MOB_SNOWBALL;
i = CONTENT_CRAFTITEM_STICK;
f = &g_content_craftitem_features[i];

View File

@ -44,6 +44,8 @@ struct CraftItemFeatures {
s16 drop_count;
// used by mobs that are picked up
content_t drop_item;
// used by snowballs and such... things that are thrown
content_t thrown_item;
CraftItemFeatures():
content(CONTENT_IGNORE),
@ -54,7 +56,8 @@ struct CraftItemFeatures {
fuel_time(0.0),
edible(0),
drop_count(-1),
drop_item(CONTENT_IGNORE)
drop_item(CONTENT_IGNORE),
thrown_item(CONTENT_IGNORE)
{}
};

View File

@ -513,4 +513,21 @@ void content_mob_init()
f->spawn_max_nearby_mobs = 3;
f->lifetime = 900.0;
f->setCollisionBox(aabb3f(-0.4*BS, 0., -0.4*BS, 0.4*BS, 1.*BS, 0.4*BS));
i = CONTENT_MOB_SNOWBALL;
f = &g_content_mob_features[i];
f->content = i;
f->level = MOB_AGGRESSIVE;
f->setTexture("snow_ball.png");
f->model_offset = v3f(0,0.2,0);
f->punch_action = MPA_IGNORE;
f->motion = MM_THROWN;
f->motion_type = MMT_FLY;
f->notices_player = true;
f->attack_player_damage = 1;
f->attack_player_range = v3f(1,1,1);
f->lifetime = 10.0;
f->contact_place_node = CONTENT_SNOW;
f->contact_drop_item = CONTENT_CRAFTITEM_SNOW_BALL;
f->setCollisionBox(aabb3f(-BS/3.,0.0,-BS/3., BS/3.,BS/2.,BS/3.));
}

View File

@ -105,6 +105,7 @@ struct MobFeatures {
MobMotion motion;
MobMotionType motion_type;
MobMotion angry_motion;
f32 static_thrown_speed;
content_t hunted_node;
content_t fleed_node;
bool notices_player;
@ -123,6 +124,8 @@ struct MobFeatures {
u16 special_dropped_max;
f32 lifetime;
u16 contact_explosion_diameter;
content_t contact_place_node;
content_t contact_drop_item;
std::string sound_death;
std::string sound_attack;
@ -225,6 +228,7 @@ struct MobFeatures {
motion = MM_STATIC;
motion_type = MMT_WALK;
angry_motion = MM_STATIC;
static_thrown_speed = 20.0;
hunted_node = CONTENT_IGNORE;
fleed_node = CONTENT_IGNORE;
notices_player = false;
@ -243,6 +247,8 @@ struct MobFeatures {
special_dropped_max = 0;
lifetime = 0.0;
contact_explosion_diameter = 0;
contact_place_node = CONTENT_IGNORE;
contact_drop_item = CONTENT_IGNORE;
sound_death = "";
sound_attack = "";
sound_punch = "mob-dig";
@ -300,5 +306,6 @@ void content_mob_init();
#define CONTENT_MOB_WOLF (CONTENT_MOB_MASK | 0x0B)
#define CONTENT_MOB_TAMEWOLF (CONTENT_MOB_MASK | 0x0C)
#define CONTENT_MOB_SHEEP (CONTENT_MOB_MASK | 0x0D)
#define CONTENT_MOB_SNOWBALL (CONTENT_MOB_MASK | 0x0E)
#endif

View File

@ -1091,12 +1091,24 @@ void MobSAO::stepMotionThrown(float dtime)
{
MobFeatures m = content_mob_features(m_content);
m_base_position += m_speed * dtime;
m_base_position.Y -= 0.1*BS*dtime;
m_speed.Y -= 10.0*BS*dtime;
v3s16 pos_i = floatToInt(m_base_position, BS);
if (!checkFreePosition(pos_i)) {
if (m.contact_explosion_diameter > 0)
explodeSquare(pos_i, v3s16(m.contact_explosion_diameter,m.contact_explosion_diameter,m.contact_explosion_diameter));
if (m.contact_place_node != CONTENT_IGNORE && checkFreeAndWalkablePosition(pos_i+v3s16(0,1,0))) {
v3s16 pos = pos_i+v3s16(0,1,0);
m_env->getMap().addNodeWithEvent(pos,MapNode(m.contact_place_node));
}else if (m.contact_drop_item != CONTENT_IGNORE) {
v3f pos = intToFloat(pos_i+v3s16(0,1,0),BS);
InventoryItem *i = InventoryItem::create(m.contact_drop_item,1);
if (i) {
ServerActiveObject *obj = i->createSAO(m_env,0,m_base_position);
if (obj)
m_env->addActiveObject(obj);
}
}
m_removed = true;
return;
}

View File

@ -1621,7 +1621,7 @@ void the_game(
{
// Read client events
for (;;) {
while (1) {
ClientEvent event = client.getClientEvent();
if (event.type == CE_NONE) {
break;
@ -1665,9 +1665,8 @@ void the_game(
//bool camera_offset_changed = (camera_offset != old_camera_offset);
if(!disable_camera_update){
client.updateCamera(camera_position,
camera_direction, camera_fov, camera_offset);
if (!disable_camera_update) {
client.updateCamera(camera_position, camera_direction, camera_fov, camera_offset);
client.updateCameraOffset(camera_offset);
client.getEnv().updateObjectsCameraOffset(camera_offset);
update_particles_camera_offset(camera_offset);
@ -1675,249 +1674,252 @@ void the_game(
clouds->updateCameraOffset(camera_offset);
}
/*
Calculate what block is the crosshair pointing to
*/
f32 d = 4; // max. distance
core::line3d<f32> shootline(camera_position,
camera_position + camera_direction * BS * (d+1));
ClientActiveObject *selected_active_object
= client.getSelectedActiveObject
(d*BS, camera_position, shootline);
bool left_punch = false;
bool left_punch_muted = false;
if (selected_active_object != NULL) {
client.setPointedContent(selected_active_object->getContent());
/* Clear possible cracking animation */
if (nodepos_old != v3s16(-32768,-32768,-32768)) {
client.clearTempMod(nodepos_old);
dig_time = 0.0;
nodepos_old = v3s16(-32768,-32768,-32768);
}
core::aabbox3d<f32> *selection_box
= selected_active_object->getSelectionBox();
// Box should exist because object was returned in the
// first place
assert(selection_box);
v3f pos = selected_active_object->getPosition()-intToFloat(camera_offset,BS);
core::aabbox3d<f32> box_on_map(
selection_box->MinEdge + pos,
selection_box->MaxEdge + pos
);
if(selected_active_object->doShowSelectionBox())
hilightboxes.push_back(box_on_map);
infotext = narrow_to_wide(selected_active_object->infoText());
if(input->getLeftState())
{
bool do_punch = false;
bool do_punch_damage = false;
if(object_hit_delay_timer <= 0.0){
do_punch = true;
do_punch_damage = true;
object_hit_delay_timer = object_hit_delay;
}
if(input->getLeftClicked()){
do_punch = true;
}
if(do_punch){
infostream<<"Left-clicked object"<<std::endl;
left_punch = true;
}
if(do_punch_damage){
client.clickActiveObject(0,
selected_active_object->getId(), g_selected_item);
}
}
else if(input->getRightClicked())
{
infostream<<"Right-clicked object"<<std::endl;
client.clickActiveObject(1,
selected_active_object->getId(), g_selected_item);
}
}
else // selected_object == NULL
{
/*
Find out which node we are pointing at
*/
bool nodefound = false;
v3s16 nodepos;
v3s16 neighbourpos;
core::aabbox3d<f32> nodehilightbox;
getPointedNode(&client, player_position,
camera_direction, camera_position,
nodefound, shootline,
nodepos, neighbourpos, camera_offset,
nodehilightbox, d);
if (!nodefound) {
if (nodepos_old != v3s16(-32768,-32768,-32768)) {
client.clearTempMod(nodepos_old);
dig_time = 0.0;
nodepos_old = v3s16(-32768,-32768,-32768);
}
InventoryItem *wield = (InventoryItem*)client.getLocalPlayer()->getWieldItem();
if (
wield
&& (
content_craftitem_features(wield->getContent()).thrown_item != CONTENT_IGNORE
//|| (
//content_toolitem_features(wield->getContent()).thrown_item != CONTENT_IGNORE
//&& client.getLocalPlayer()->inventory.find(content_toolitem_features(wield->getContent()).thrown_item) > -1
//)
) && input->getLeftClicked()
) {
client.throwItem(camera_direction,g_selected_item);
}else{
/*
Check information text of node
Calculate what block is the crosshair pointing to
*/
if (nodepos != nodepos_old && nodepos_old != v3s16(-32768,-32768,-32768))
client.clearTempMod(nodepos_old);
NodeMetadata *meta = client.getNodeMetadata(nodepos);
if (meta)
infotext = meta->infoText();
f32 d = 4; // max. distance
core::line3d<f32> shootline(camera_position, camera_position + camera_direction * BS * (d+1));
/*
Handle digging
*/
ClientActiveObject *selected_active_object = client.getSelectedActiveObject(d*BS, camera_position, shootline);
if (input->getLeftReleased()) {
client.clearTempMod(nodepos);
dig_time = 0.0;
}
/*
Visualize selection
*/
if (selected_active_object != NULL) {
client.setPointedContent(selected_active_object->getContent());
/* Clear possible cracking animation */
if (nodepos_old != v3s16(-32768,-32768,-32768)) {
client.clearTempMod(nodepos_old);
dig_time = 0.0;
nodepos_old = v3s16(-32768,-32768,-32768);
}
if (g_settings->exists("selected_node") && g_settings->get("selected_node") == "outline") {
hilightboxes.push_back(nodehilightbox);
}else{
client.setTempMod(nodepos, NodeMod(NODEMOD_SELECTION));
}
core::aabbox3d<f32> *selection_box
= selected_active_object->getSelectionBox();
// Box should exist because object was returned in the
// first place
assert(selection_box);
if (nodig_delay_counter > 0.0) {
nodig_delay_counter -= dtime;
}else{
if (nodepos != nodepos_old) {
infostream<<"Pointing at ("<<nodepos.X<<","
<<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
v3f pos = selected_active_object->getPosition()-intToFloat(camera_offset,BS);
core::aabbox3d<f32> box_on_map(
selection_box->MinEdge + pos,
selection_box->MaxEdge + pos
);
if (selected_active_object->doShowSelectionBox())
hilightboxes.push_back(box_on_map);
infotext = narrow_to_wide(selected_active_object->infoText());
if (input->getLeftState()) {
bool do_punch = false;
bool do_punch_damage = false;
if (object_hit_delay_timer <= 0.0){
do_punch = true;
do_punch_damage = true;
object_hit_delay_timer = object_hit_delay;
}
if (input->getLeftClicked()) {
do_punch = true;
}
if (do_punch) {
infostream<<"Left-clicked object"<<std::endl;
left_punch = true;
}
if (do_punch_damage) {
client.clickActiveObject(0, selected_active_object->getId(), g_selected_item);
}
}else if (input->getRightClicked()) {
infostream<<"Right-clicked object"<<std::endl;
client.clickActiveObject(1, selected_active_object->getId(), g_selected_item);
}
}else{ // selected_object == NULL
/*
Find out which node we are pointing at
*/
bool nodefound = false;
v3s16 nodepos;
v3s16 neighbourpos;
core::aabbox3d<f32> nodehilightbox;
getPointedNode(&client, player_position,
camera_direction, camera_position,
nodefound, shootline,
nodepos, neighbourpos, camera_offset,
nodehilightbox, d);
if (!nodefound) {
if (nodepos_old != v3s16(-32768,-32768,-32768)) {
client.clearTempMod(nodepos_old);
dig_time = 0.0;
nodepos_old = v3s16(-32768,-32768,-32768);
}
}
if (input->getLeftClicked() || (input->getLeftState() && nodepos != nodepos_old)) {
infostream<<"Started digging"<<std::endl;
client.groundAction(0, nodepos, neighbourpos, g_selected_item);
}
if (input->getLeftClicked())
client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
if (input->getLeftState()) {
MapNode n = client.getNode(nodepos);
// Get tool name. Default is "" = bare hands
content_t toolid = CONTENT_IGNORE;
InventoryList *mlist = local_inventory.getList("main");
if (mlist != NULL) {
InventoryItem *item = mlist->getItem(g_selected_item);
if (item && (std::string)item->getName() == "ToolItem") {
ToolItem *titem = (ToolItem*)item;
toolid = titem->getContent();
}
}
// Get digging properties for material and tool
content_t material = n.getContent();
DiggingProperties prop = getDiggingProperties(material, toolid);
float dig_time_complete = 0.0;
if (prop.diggable == false) {
dig_time_complete = 10000000.0;
client.clearTempMod(nodepos);
}else{
dig_time_complete = prop.time;
if (g_settings->getBool("enable_particles"))
addPunchingParticles(smgr, player, nodepos, content_features(n).tiles);
if (dig_time_complete >= 0.001) {
dig_index = (u16)((float)CRACK_ANIMATION_LENGTH
* dig_time/dig_time_complete);
}else {
// This is for torches
dig_index = CRACK_ANIMATION_LENGTH;
}
if (dig_index < CRACK_ANIMATION_LENGTH) {
client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
}else{
infostream<<"Digging completed"<<std::endl;
client.groundAction(3, nodepos, neighbourpos, g_selected_item);
client.clearTempMod(nodepos);
client.removeNode(nodepos);
if (g_settings->getBool("enable_particles"))
addDiggingParticles(smgr, player, nodepos, content_features(n).tiles);
dig_time = 0;
nodig_delay_counter = dig_time_complete
/ (float)CRACK_ANIMATION_LENGTH;
// We don't want a corresponding delay to
// very time consuming nodes
if (nodig_delay_counter > 0.5)
nodig_delay_counter = 0.5;
// We want a slight delay to very little
// time consuming nodes
float mindelay = 0.15;
if (nodig_delay_counter < mindelay)
nodig_delay_counter = mindelay;
}
}
dig_time += dtime;
camera.setDigging(0); // left click animation
}
}
if (input->getRightClicked()) {
infostream<<"Ground right-clicked"<<std::endl;
// If metadata provides an inventory view, activate it
if (meta && meta->getDrawSpecString() != "" && !random_input) {
infostream<<"Launching custom inventory view"<<std::endl;
InventoryLocation inventoryloc;
inventoryloc.setNodeMeta(nodepos);
/* Create menu */
GUIFormSpecMenu *menu = new GUIFormSpecMenu(guienv, guiroot, -1, &g_menumgr, &client);
menu->setFormSpec(meta->getDrawSpecString(), inventoryloc);
menu->setFormIO(new NodeMetadataFormIO(nodepos, &client));
menu->drop();
}else{
client.groundAction(1, nodepos, neighbourpos, g_selected_item);
camera.setDigging(1); // right click animation
/*
Check information text of node
*/
if (nodepos != nodepos_old && nodepos_old != v3s16(-32768,-32768,-32768))
client.clearTempMod(nodepos_old);
NodeMetadata *meta = client.getNodeMetadata(nodepos);
if (meta)
infotext = meta->infoText();
/*
Handle digging
*/
if (input->getLeftReleased()) {
client.clearTempMod(nodepos);
dig_time = 0.0;
}
/*
Visualize selection
*/
if (g_settings->exists("selected_node") && g_settings->get("selected_node") == "outline") {
hilightboxes.push_back(nodehilightbox);
}else{
client.setTempMod(nodepos, NodeMod(NODEMOD_SELECTION));
}
if (nodig_delay_counter > 0.0) {
nodig_delay_counter -= dtime;
}else{
if (nodepos != nodepos_old) {
infostream<<"Pointing at ("<<nodepos.X<<","
<<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
if (nodepos_old != v3s16(-32768,-32768,-32768)) {
client.clearTempMod(nodepos_old);
dig_time = 0.0;
nodepos_old = v3s16(-32768,-32768,-32768);
}
}
if (input->getLeftClicked() || (input->getLeftState() && nodepos != nodepos_old)) {
infostream<<"Started digging"<<std::endl;
client.groundAction(0, nodepos, neighbourpos, g_selected_item);
}
if (input->getLeftClicked())
client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
if (input->getLeftState()) {
MapNode n = client.getNode(nodepos);
// Get tool name. Default is "" = bare hands
content_t toolid = CONTENT_IGNORE;
InventoryList *mlist = local_inventory.getList("main");
if (mlist != NULL) {
InventoryItem *item = mlist->getItem(g_selected_item);
if (item && (std::string)item->getName() == "ToolItem") {
ToolItem *titem = (ToolItem*)item;
toolid = titem->getContent();
}
}
// Get digging properties for material and tool
content_t material = n.getContent();
DiggingProperties prop = getDiggingProperties(material, toolid);
float dig_time_complete = 0.0;
if (prop.diggable == false) {
dig_time_complete = 10000000.0;
client.clearTempMod(nodepos);
}else{
dig_time_complete = prop.time;
if (g_settings->getBool("enable_particles"))
addPunchingParticles(smgr, player, nodepos, content_features(n).tiles);
if (dig_time_complete >= 0.001) {
dig_index = (u16)((float)CRACK_ANIMATION_LENGTH
* dig_time/dig_time_complete);
}else {
// This is for torches
dig_index = CRACK_ANIMATION_LENGTH;
}
if (dig_index < CRACK_ANIMATION_LENGTH) {
client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
}else{
infostream<<"Digging completed"<<std::endl;
client.groundAction(3, nodepos, neighbourpos, g_selected_item);
client.clearTempMod(nodepos);
client.removeNode(nodepos);
if (g_settings->getBool("enable_particles"))
addDiggingParticles(smgr, player, nodepos, content_features(n).tiles);
dig_time = 0;
nodig_delay_counter = dig_time_complete
/ (float)CRACK_ANIMATION_LENGTH;
// We don't want a corresponding delay to
// very time consuming nodes
if (nodig_delay_counter > 0.5)
nodig_delay_counter = 0.5;
// We want a slight delay to very little
// time consuming nodes
float mindelay = 0.15;
if (nodig_delay_counter < mindelay)
nodig_delay_counter = mindelay;
}
}
dig_time += dtime;
camera.setDigging(0); // left click animation
}
}
if (input->getRightClicked()) {
infostream<<"Ground right-clicked"<<std::endl;
// If metadata provides an inventory view, activate it
if (meta && meta->getDrawSpecString() != "" && !random_input) {
infostream<<"Launching custom inventory view"<<std::endl;
InventoryLocation inventoryloc;
inventoryloc.setNodeMeta(nodepos);
/* Create menu */
GUIFormSpecMenu *menu = new GUIFormSpecMenu(guienv, guiroot, -1, &g_menumgr, &client);
menu->setFormSpec(meta->getDrawSpecString(), inventoryloc);
menu->setFormIO(new NodeMetadataFormIO(nodepos, &client));
menu->drop();
}else{
client.groundAction(1, nodepos, neighbourpos, g_selected_item);
camera.setDigging(1); // right click animation
}
}
nodepos_old = nodepos;
}
}
nodepos_old = nodepos;
} // selected_object == NULL
}
} // selected_object == NULL
if (left_punch || (input->getLeftClicked() && !left_punch_muted))
camera.setDigging(0); // left click animation
@ -2026,8 +2028,7 @@ void the_game(
/*
Update gui stuff (0ms)
*/
const char program_name_and_version[] =
"Voxelands " VERSION_STRING;
const char program_name_and_version[] = "Voxelands " VERSION_STRING;
if (show_debug) {
static float drawtime_avg = 0;
drawtime_avg = drawtime_avg * 0.95 + (float)drawtime*0.05;

View File

@ -44,6 +44,7 @@
#include "content_nodemeta.h"
#include "mapblock.h"
#include "serverobject.h"
#include "content_sao.h"
#include "settings.h"
#include "profiler.h"
#include "log.h"
@ -2102,9 +2103,77 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
return;
}
switch (command) {
case TOSERVER_THROWITEM:
{
if (datasize < 2+12+12+2)
return;
v3s32 ps = readV3S32(&data[2]);
v3s32 ss = readV3S32(&data[2+12]);
u16 item_i = readU16(&data[2+12+12]);
InventoryList *ilist = player->inventory.getList("main");
if (ilist == NULL)
return;
// Get item
InventoryItem *item = ilist->getItem(item_i);
// If there is no item, it is not possible to throw it
if (item == NULL)
return;
content_t thrown = content_craftitem_features(item->getContent()).thrown_item;
// We can throw it, right?
if (thrown == CONTENT_IGNORE)
return;
if (g_settings->getBool("droppable_inventory") == false || (getPlayerPrivs(player) & PRIV_BUILD) == 0) {
infostream<<"Not allowing player to drop item: creative mode and no build privs"<<std::endl;
return;
}
v3f pf((f32)ps.X/100., (f32)ps.Y/100., (f32)ps.Z/100.);
v3f sf((f32)ss.X/100., (f32)ss.Y/100., (f32)ss.Z/100.);
ServerActiveObject *obj = new MobSAO(&m_env, 0, pf, sf*content_mob_features(thrown).static_thrown_speed*BS, thrown);
if (obj == NULL) {
infostream<<"WARNING: item resulted in NULL object, "
<<"not throwing into map"
<<std::endl;
}else{
actionstream<<player->getName()<<" throws "<<thrown<<" at "<<PP(pf/BS)<<" ("<<PP(sf/BS)")"<<std::endl;
// Add the object to the environment
m_env.addActiveObject(obj);
}
if (g_settings->getBool("infinite_inventory") == false) {
// Delete the right amount of items from the slot
// Delete item if all gone
if (item->getCount() <= 1) {
if (item->getCount() < 1)
infostream<<"WARNING: Server: dropped more items"
<<" than the slot contains"<<std::endl;
InventoryList *ilist = player->inventory.getList("main");
if (ilist)
// Remove from inventory and send inventory
ilist->deleteItem(item_i);
}else{
item->remove(1);
}
// Send inventory
UpdateCrafting(peer_id);
SendInventory(peer_id);
}
}
break;
case TOSERVER_PLAYERPOS:
{
if(datasize < 2+12+12+4+4)
if (datasize < 2+12+12+4+4)
return;
u32 start = 0;