aboutsummaryrefslogtreecommitdiff
path: root/src/script/lua_api/l_object.h
Commit message (Expand)AuthorAge
* Clean up l_object.cpp (#10512)Zughy2020-10-22
* Add ObjectRef:get_children() (#10480)Zughy2020-10-13
* Minimap as HUD element with API controlPierre-Yves Rollo2020-10-04
* Deprecate get_player_velocity and add_player_velocity (#10173)rubenwardy2020-10-04
* set_sky improvements, set_sun, set_moon and set_starsJordach2020-03-05
* Add support for per-player FOV overrides and multipliersAnand S2019-09-19
* Implement adding velocity to player from Luasfan52019-08-10
* Force send a mapblock to a player (#8140)sofar2019-04-28
* Add Lua methods 'set_rotation()' and 'get_rotation()' (#7395)CoderForTheBetter2018-11-28
* Log deprecated Lua function calls (#7491)SmallJoker2018-07-01
* Add player:get_meta(), deprecate player attributes (#7202)rubenwardy2018-04-06
* ObjectRef: Add add_velocity() (#3208)you2018-03-31
* Add formspec theming using prepended stringsAndrew Ward2018-03-28
* Fix animation frame_speed and blend loosing precision due to incorrec… (#6357)sapier2017-09-01
* Code modernization: subfolders (#6283)Loïc Blot2017-08-19
* C++ modernize: Pragma once (#6264)Loïc Blot2017-08-17
* C++11 cleanup on constructors (#6000)Vincent Glize2017-06-19
* Set sky API: Add bool for clouds in front of custom skyboxparamat2017-05-02
* Add clouds APIBen Deutsch2017-04-30
* Sneak: Add option for old move codeparamat2017-04-17
* Replace luaL_reg with luaL_Reg as recent LuaJIT dropped the Lua 5.0 compat (#...Loïc Blot2017-04-08
* Implement player attribute backend (#4155)Loïc Blot2017-01-27
* Add Entity get_texture_mod() to Lua APIsapier2017-01-21
* Rename ObjectRef methods to be consistent and predictablerubenwardy2017-01-16
* Move RemotePlayer code to its own cpp/headerLoic Blot2016-10-08
* Player/LocalPlayer/RemotePlayer inheritance cleanup (part 2 on X)Loic Blot2016-10-08
* Player/LocalPlayer/RemotePlayer inheritance cleanup (part 1 on X)Loic Blot2016-10-08
* Player: New get_look, set_look APIraymoo2016-06-24
* Added get_player_velocity() method. Fixes #1176Elia Argentieri2015-07-20
* Fix some issues with animations, and allow non-looped animations to be definedMirceaKitsune2015-06-22
* Add some missing getter functions to the lua APITeTpaAka2015-05-28
* Generalize core.get/set_nametag_color into core.get/set_nametag_attributesTeTpaAka2015-05-15
* Add get and set functions for the nametag colorTeTpaAka2015-05-15
* is_player() is no player-only functionest312015-05-12
* Revert "Add a Lua call to do damages / heals" ok @ShadowNinjaLoic Blot2015-03-22
* Add a Lua call to do damages / healsLoic Blot2015-03-18
* Fix heart + bubble bar size on different texture packssapier2014-05-07
* Add player:set_eye_offset() by @MirceaKitsune and clean upBlockMen2014-04-12
* Add third person viewBlockMen2014-04-12
* Add player:override_day_night_ratio() for arbitrarily controlling sunlight br...Perttu Ahola2014-02-01
* Add player:set_sky() with simple skybox supportPerttu Ahola2014-02-01
* Add sneak and sneak_glitch to set_physics_override()PilzAdam2013-12-03
* Fix issue #1009 (minetest.get_connected_players() returns non-existing players)kwolekr2013-11-17
* Use player:set_hotbar_image() instead of hardcoded hotbar.pngPilzAdam2013-09-05
* Omnicleanup: header cleanup, add ModApiUtil shared between game and mainmenuKahrl2013-08-14
* Add set_breath and get_breath to lua API.RealBadAngel2013-07-20
* Add ObjectRef.hud_set_hotbar_itemcount and add TOCLIENT_HUD_SET_PARAMKahrl2013-05-26
* Move scriptapi to separate folder (by sapier)sapier2013-05-25
a> 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
/*
Minetest
Copyright (C) 2010-2017 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "util/serialize.h"
#include "util/pointedthing.h"
#include "client.h"
#include "clientenvironment.h"
#include "clientsimpleobject.h"
#include "clientmap.h"
#include "scripting_client.h"
#include "mapblock_mesh.h"
#include "mtevent.h"
#include "collision.h"
#include "nodedef.h"
#include "profiler.h"
#include "raycast.h"
#include "voxelalgorithms.h"
#include "settings.h"
#include "shader.h"
#include "content_cao.h"
#include <algorithm>
#include "client/renderingengine.h"

/*
	CAOShaderConstantSetter
*/

//! Shader constant setter for passing material emissive color to the CAO object_shader
class CAOShaderConstantSetter : public IShaderConstantSetter
{
public:
	CAOShaderConstantSetter():
			m_emissive_color_setting("emissiveColor")
	{}

	~CAOShaderConstantSetter() override = default;

	void onSetConstants(video::IMaterialRendererServices *services) override
	{
		// Ambient color
		video::SColorf emissive_color(m_emissive_color);

		float as_array[4] = {
			emissive_color.r,
			emissive_color.g,
			emissive_color.b,
			emissive_color.a,
		};
		m_emissive_color_setting.set(as_array, services);
	}

	void onSetMaterial(const video::SMaterial& material) override
	{
		m_emissive_color = material.EmissiveColor;
	}

private:
	video::SColor m_emissive_color;
	CachedPixelShaderSetting<float, 4> m_emissive_color_setting;
};

class CAOShaderConstantSetterFactory : public IShaderConstantSetterFactory
{
public:
	CAOShaderConstantSetterFactory()
	{}

	virtual IShaderConstantSetter* create()
	{
		return new CAOShaderConstantSetter();
	}
};

/*
	ClientEnvironment
*/

ClientEnvironment::ClientEnvironment(ClientMap *map,
	ITextureSource *texturesource, Client *client):
	Environment(client),
	m_map(map),
	m_texturesource(texturesource),
	m_client(client)
{
	auto *shdrsrc = m_client->getShaderSource();
	shdrsrc->addShaderConstantSetterFactory(new CAOShaderConstantSetterFactory());
}

ClientEnvironment::~ClientEnvironment()
{
	m_ao_manager.clear();

	for (auto &simple_object : m_simple_objects) {
		delete simple_object;
	}

	// Drop/delete map
	m_map->drop();

	delete m_local_player;
}

Map & ClientEnvironment::getMap()
{
	return *m_map;
}

ClientMap & ClientEnvironment::getClientMap()
{
	return *m_map;
}

void ClientEnvironment::setLocalPlayer(LocalPlayer *player)
{
	/*
		It is a failure if already is a local player
	*/
	FATAL_ERROR_IF(m_local_player != NULL,
		"Local player already allocated");

	m_local_player = player;
}

void ClientEnvironment::step(float dtime)
{
	/* Step time of day */
	stepTimeOfDay(dtime);

	// Get some settings
	bool fly_allowed = m_client->checkLocalPrivilege("fly");
	bool free_move = fly_allowed && g_settings->getBool("free_move");

	// Get local player
	LocalPlayer *lplayer = getLocalPlayer();
	assert(lplayer);
	// collision info queue
	std::vector<CollisionInfo> player_collisions;

	/*
		Get the speed the player is going
	*/
	bool is_climbing = lplayer->is_climbing;

	f32 player_speed = lplayer->getSpeed().getLength();

	/*
		Maximum position increment
	*/
	//f32 position_max_increment = 0.05*BS;
	f32 position_max_increment = 0.1*BS;

	// Maximum time increment (for collision detection etc)
	// time = distance / speed
	f32 dtime_max_increment = 1;
	if(player_speed > 0.001)
		dtime_max_increment = position_max_increment / player_speed;

	// Maximum time increment is 10ms or lower
	if(dtime_max_increment > 0.01)
		dtime_max_increment = 0.01;

	// Don't allow overly huge dtime
	if(dtime > 0.5)
		dtime = 0.5;

	/*
		Stuff that has a maximum time increment
	*/

	u32 steps = ceil(dtime / dtime_max_increment);
	f32 dtime_part = dtime / steps;
	for (; steps > 0; --steps) {
		/*
			Local player handling
		*/

		// Control local player
		lplayer->applyControl(dtime_part, this);

		// Apply physics
		if (!free_move && !is_climbing) {
			// Gravity
			v3f speed = lplayer->getSpeed();
			if (!lplayer->in_liquid)
				speed.Y -= lplayer->movement_gravity *
					lplayer->physics_override_gravity * dtime_part * 2.0f;

			// Liquid floating / sinking
			if (lplayer->in_liquid && !lplayer->swimming_vertical &&
					!lplayer->swimming_pitch)
				speed.Y -= lplayer->movement_liquid_sink * dtime_part * 2.0f;

			// Liquid resistance
			if (lplayer->in_liquid_stable || lplayer->in_liquid) {
				// How much the node's viscosity blocks movement, ranges
				// between 0 and 1. Should match the scale at which viscosity
				// increase affects other liquid attributes.
				static const f32 viscosity_factor = 0.3f;

				v3f d_wanted = -speed / lplayer->movement_liquid_fluidity;
				f32 dl = d_wanted.getLength();
				if (dl > lplayer->movement_liquid_fluidity_smooth)
					dl = lplayer->movement_liquid_fluidity_smooth;

				dl *= (lplayer->liquid_viscosity * viscosity_factor) +
					(1 - viscosity_factor);
				v3f d = d_wanted.normalize() * (dl * dtime_part * 100.0f);
				speed += d;
			}

			lplayer->setSpeed(speed);
		}

		/*
			Move the lplayer.
			This also does collision detection.
		*/
		lplayer->move(dtime_part, this, position_max_increment,
			&player_collisions);
	}

	bool player_immortal = false;
	f32 player_fall_factor = 1.0f;
	GenericCAO *playercao = lplayer->getCAO();
	if (playercao) {
		player_immortal = playercao->isImmortal();
		int addp_p = itemgroup_get(playercao->getGroups(),
			"fall_damage_add_percent");
		// convert armor group into an usable fall damage factor
		player_fall_factor = 1.0f + (float)addp_p / 100.0f;
	}

	for (const CollisionInfo &info : player_collisions) {
		v3f speed_diff = info.new_speed - info.old_speed;;
		// Handle only fall damage
		// (because otherwise walking against something in fast_move kills you)
		if (speed_diff.Y < 0 || info.old_speed.Y >= 0)
			continue;
		// Get rid of other components
		speed_diff.X = 0;
		speed_diff.Z = 0;
		f32 pre_factor = 1; // 1 hp per node/s
		f32 tolerance = BS*14; // 5 without damage
		if (info.type == COLLISION_NODE) {
			const ContentFeatures &f = m_client->ndef()->
				get(m_map->getNode(info.node_p));
			// Determine fall damage modifier
			int addp_n = itemgroup_get(f.groups, "fall_damage_add_percent");
			// convert node group to an usable fall damage factor
			f32 node_fall_factor = 1.0f + (float)addp_n / 100.0f;
			// combine both player fall damage modifiers
			pre_factor = node_fall_factor * player_fall_factor;
		}
		float speed = pre_factor * speed_diff.getLength();

		if (speed > tolerance && !player_immortal && pre_factor > 0.0f) {
			f32 damage_f = (speed - tolerance) / BS;
			u16 damage = (u16)MYMIN(damage_f + 0.5, U16_MAX);
			if (damage != 0) {
				damageLocalPlayer(damage, true);
				m_client->getEventManager()->put(
					new SimpleTriggerEvent(MtEvent::PLAYER_FALLING_DAMAGE));
			}
		}
	}

	if (m_client->modsLoaded())
		m_script->environment_step(dtime);

	// Update lighting on local player (used for wield item)
	u32 day_night_ratio = getDayNightRatio();
	{
		// Get node at head

		// On InvalidPositionException, use this as default
		// (day: LIGHT_SUN, night: 0)
		MapNode node_at_lplayer(CONTENT_AIR, 0x0f, 0);

		v3s16 p = lplayer->getLightPosition();
		node_at_lplayer = m_map->getNode(p);

		u16 light = getInteriorLight(node_at_lplayer, 0, m_client->ndef());
		final_color_blend(&lplayer->light_color, light, day_night_ratio);
	}

	/*
		Step active objects and update lighting of them
	*/

	bool update_lighting = m_active_object_light_update_interval.step(dtime, 0.21);
	auto cb_state = [this, dtime, update_lighting, day_night_ratio] (ClientActiveObject *cao) {
		// Step object
		cao->step(dtime, this);

		if (update_lighting)
			cao->updateLight(day_night_ratio);
	};

	m_ao_manager.step(dtime, cb_state);

	/*
		Step and handle simple objects
	*/
	g_profiler->avg("ClientEnv: CSO count [#]", m_simple_objects.size());
	for (auto i = m_simple_objects.begin(); i != m_simple_objects.end();) {
		ClientSimpleObject *simple = *i;

		simple->step(dtime);
		if(simple->m_to_be_removed) {
			delete simple;
			i = m_simple_objects.erase(i);
		}
		else {
			++i;
		}
	}
}

void ClientEnvironment::addSimpleObject(ClientSimpleObject *simple)
{
	m_simple_objects.push_back(simple);
}

GenericCAO* ClientEnvironment::getGenericCAO(u16 id)
{
	ClientActiveObject *obj = getActiveObject(id);
	if (obj && obj->getType() == ACTIVEOBJECT_TYPE_GENERIC)
		return (GenericCAO*) obj;

	return NULL;
}

u16 ClientEnvironment::addActiveObject(ClientActiveObject *object)
{
	// Register object. If failed return zero id
	if (!m_ao_manager.registerObject(object))
		return 0;

	object->addToScene(m_texturesource, m_client->getSceneManager());

	// Update lighting immediately
	object->updateLight(getDayNightRatio());
	return object->getId();
}

void ClientEnvironment::addActiveObject(u16 id, u8 type,
	const std::string &init_data)
{
	ClientActiveObject* obj =
		ClientActiveObject::create((ActiveObjectType) type, m_client, this);
	if(obj == NULL)
	{
		infostream<<"ClientEnvironment::addActiveObject(): "
			<<"id="<<id<<" type="<<type<<": Couldn't create object"
			<<std::endl;
		return;
	}

	obj->setId(id);

	try
	{
		obj->initialize(init_data);
	}
	catch(SerializationError &e)
	{
		errorstream<<"ClientEnvironment::addActiveObject():"
			<<" id="<<id<<" type="<<type
			<<": SerializationError in initialize(): "
			<<e.what()
			<<": init_data="<<serializeJsonString(init_data)
			<<std::endl;
	}

	u16 new_id = addActiveObject(obj);
	// Object initialized:
	if ((obj = getActiveObject(new_id))) {
		// Final step is to update all children which are already known
		// Data provided by AO_CMD_SPAWN_INFANT
		const auto &children = obj->getAttachmentChildIds();
		for (auto c_id : children) {
			if (auto *o = getActiveObject(c_id))
				o->updateAttachments();
		}
	}
}


void ClientEnvironment::removeActiveObject(u16 id)
{
	// Get current attachment childs to detach them visually
	std::unordered_set<int> attachment_childs;
	if (auto *obj = getActiveObject(id))
		attachment_childs = obj->getAttachmentChildIds();

	m_ao_manager.removeObject(id);

	// Perform a proper detach in Irrlicht
	for (auto c_id : attachment_childs) {
		if (ClientActiveObject *child = getActiveObject(c_id))
			child->updateAttachments();
	}
}

void ClientEnvironment::processActiveObjectMessage(u16 id, const std::string &data)
{
	ClientActiveObject *obj = getActiveObject(id);
	if (obj == NULL) {
		infostream << "ClientEnvironment::processActiveObjectMessage():"
			<< " got message for id=" << id << ", which doesn't exist."
			<< std::endl;
		return;
	}

	try {
		obj->processMessage(data);
	} catch (SerializationError &e) {
		errorstream<<"ClientEnvironment::processActiveObjectMessage():"
			<< " id=" << id << " type=" << obj->getType()
			<< " SerializationError in processMessage(): " << e.what()
			<< std::endl;
	}
}

/*
	Callbacks for activeobjects
*/

void ClientEnvironment::damageLocalPlayer(u16 damage, bool handle_hp)
{
	LocalPlayer *lplayer = getLocalPlayer();
	assert(lplayer);

	if (handle_hp) {
		if (lplayer->hp > damage)
			lplayer->hp -= damage;
		else
			lplayer->hp = 0;
	}

	ClientEnvEvent event;
	event.type = CEE_PLAYER_DAMAGE;
	event.player_damage.amount = damage;
	event.player_damage.send_to_server = handle_hp;
	m_client_event_queue.push(event);
}

/*
	Client likes to call these
*/

ClientEnvEvent ClientEnvironment::getClientEnvEvent()
{
	FATAL_ERROR_IF(m_client_event_queue.empty(),
			"ClientEnvironment::getClientEnvEvent(): queue is empty");

	ClientEnvEvent event = m_client_event_queue.front();
	m_client_event_queue.pop();
	return event;
}

void ClientEnvironment::getSelectedActiveObjects(
	const core::line3d<f32> &shootline_on_map,
	std::vector<PointedThing> &objects)
{
	std::vector<DistanceSortedActiveObject> allObjects;
	getActiveObjects(shootline_on_map.start,
		shootline_on_map.getLength() + 10.0f, allObjects);
	const v3f line_vector = shootline_on_map.getVector();

	for (const auto &allObject : allObjects) {
		ClientActiveObject *obj = allObject.obj;
		aabb3f selection_box;
		if (!obj->getSelectionBox(&selection_box))
			continue;

		const v3f &pos = obj->getPosition();
		aabb3f offsetted_box(selection_box.MinEdge + pos,
			selection_box.MaxEdge + pos);

		v3f current_intersection;
		v3s16 current_normal;
		if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
				&current_intersection, &current_normal)) {
			objects.emplace_back((s16) obj->getId(), current_intersection, current_normal,
				(current_intersection - shootline_on_map.start).getLengthSQ());
		}
	}
}