aboutsummaryrefslogtreecommitdiff
path: root/advtrains_line_automation/railwaytime.lua
diff options
context:
space:
mode:
authororwell96 <orwell@bleipb.de>2019-06-19 09:56:30 +0200
committerorwell96 <orwell@bleipb.de>2019-06-19 09:56:30 +0200
commit24e56dbfc2b29229ed6253b84635a16db87659fb (patch)
treef098bf7e85fde9d8c9221f5bf155dfc37ef52c8c /advtrains_line_automation/railwaytime.lua
parentd6cfa7dbf62887ba4b408900946ade28f7ca12c3 (diff)
downloadadvtrains-24e56dbfc2b29229ed6253b84635a16db87659fb.tar.gz
advtrains-24e56dbfc2b29229ed6253b84635a16db87659fb.tar.bz2
advtrains-24e56dbfc2b29229ed6253b84635a16db87659fb.zip
Railway Time: Basic time counter and utility functions
Diffstat (limited to 'advtrains_line_automation/railwaytime.lua')
-rw-r--r--advtrains_line_automation/railwaytime.lua132
1 files changed, 132 insertions, 0 deletions
diff --git a/advtrains_line_automation/railwaytime.lua b/advtrains_line_automation/railwaytime.lua
new file mode 100644
index 0000000..d117af1
--- /dev/null
+++ b/advtrains_line_automation/railwaytime.lua
@@ -0,0 +1,132 @@
+-- railwaytime.lua
+-- Advtrains uses a desynchronized time for train movement. Everything is counted relative to this time counter.
+-- The advtrains-internal time is in no way synchronized to the real-life time, due to:
+-- - Lag
+-- - Server stops/restarts
+-- However, this means that implementing a "timetable" system using the "real time" is not practical. Therefore,
+-- we introduce a custom time system, the RWT(Railway Time), which has nothing to do with RLT(Real-Life Time)
+-- RWT has a time cycle of 1 hour. This should be sufficient for most train lines that will ever be built in Minetest.
+-- A RWT looks like this: 37;25
+-- The ; is to distinguish it from a normal RLT (which has colons e.g. 12:34:56). Left number is minutes, right number is seconds.
+-- The minimum RWT is 00;00, the maximum is 59;59.
+-- It is OK to leave one places out at either end, esp. when writing relative times, such as:
+-- 43;3 22;0 2;30 0;10 ;10
+-- Those places are then filled with zeroes. Indeed, ";" would be valid for 00;00 .
+
+--[[
+1;23;45 = {
+ s=45,
+ m=23,
+ c=1, -- Cycle(~hour), not displayed most time
+}
+
+]]
+
+local rwt = {}
+
+--Time Stamp (Seconds since start of world)
+local e_time = 0
+
+-- Current rw time, cached and updated each step
+local crwtime
+
+function rwt.set_time(t)
+ e_time = t or 0
+end
+
+function rwt.get_time()
+ return e_time
+end
+
+function rwt.step(dt)
+ e_time = e_time + dt
+end
+
+function rwt.now()
+ return rwt.from_sec(e_time)
+end
+
+function rwt.new(c, m, s)
+ return {
+ c = c or 0,
+ m = m or 0,
+ s = s or 0
+ }
+end
+function rwt.copy(rwtime)
+ return {
+ c = rwtime.c or 0,
+ m = rwtime.m or 0,
+ s = rwtime.s or 0
+ }
+end
+
+function rwt.from_sec(stime)
+ local res = {}
+ local seconds = atfloor(stime)
+ res.s = seconds % 60
+ local minutes = atfloor(seconds/60)
+ res.m = minutes % 60
+ res.c = atfloor(minutes/60)
+ return res
+end
+
+function rwt.to_sec(rwtime, c_over)
+ return (c_over or rwtime.c)*60*60 + rwtime.m*60 + rwtime.s
+end
+
+function rwt.add(t1, t2)
+ local t1s = rwt.to_sec(t1)
+ local t2s = rwt.to_sec(t1)
+ return rwt.from_sec(t1s + t2s)
+end
+
+function rwt.sub(t1, t2)
+ local t1s = rwt.to_sec(t1)
+ local t2s = rwt.to_sec(t1)
+ return rwt.from_sec(t1s - t2s)
+end
+
+-- Threshold values
+-- "reftime" is the time to which this is made relative and defaults to now.
+rwt.CA_FUTURE = 60*60 - 1 -- Selected so that time lies at or in the future of reftime (at nearest point in time)
+rwt.CA_FUTURES = 60*60 -- Same, except when times are equal, advances one full cycle
+rwt.CA_PAST = 0 -- Selected so that time lies at or in the past of reftime
+rwt.CA_PASTS = -1 -- Same, except when times are equal, goes back one full cycle
+rwt.CA_CENTER = 30*60 -- If time is within past 30 minutes of reftime, selected as past, else selected as future.
+
+-- Adjusts the "cycle" value of a railway time to be in some relation to reftime.
+-- Modifies rwtime in-place
+function rwt.adjust(rwtime, reftime_p, thresh)
+ local reftime = reftime_p or rwt.now()
+
+ local reftimes = rwt.to_sec(reftime)
+
+ local rwtimes = rwt.to_sec(rwtime, 0)
+ local timeres = reftimes + thresh - rwtimes
+ local cycles = atfloor(timeres / (60*60))
+
+ rwtime.c = cycles
+end
+
+function rwt.get_adjust(rwtime, reftime, thresh)
+ local res = rwt.copy(rwtime)
+ rwt.adjust(res, reftime, thresh)
+ return res
+end
+
+function rwt.to_string(rwtime, places)
+ local pl = places or 2
+ if rwtime.c~=0 or pl>=3 then
+ return string.format("%d;%02d;%02d", rwtime.c, rwtime.m, rwtime.s)
+ elseif rwtime.m~=0 or pl>=2 then
+ return string.format("%02d;%02d", rwtime.m, rwtime.s)
+ else
+ return string.format(";%02d",rwtime.s)
+ end
+ return str
+end
+
+
+
+advtrains.lines.rwt = rwt
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
/*
Minetest
Copyright (C) 2013 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 "particles.h"
#include "constants.h"
#include "debug.h"
#include "main.h" // For g_profiler and g_settings
#include "settings.h"
#include "tile.h"
#include "gamedef.h"
#include "collision.h"
#include <stdlib.h>
#include "util/numeric.h"
#include "light.h"
#include "environment.h"
#include "clientmap.h"
#include "mapnode.h"

/*
	Utility
*/

v3f random_v3f(v3f min, v3f max)
{
	return v3f( rand()/(float)RAND_MAX*(max.X-min.X)+min.X,
			rand()/(float)RAND_MAX*(max.Y-min.Y)+min.Y,
			rand()/(float)RAND_MAX*(max.Z-min.Z)+min.Z);
}

std::vector<Particle*> all_particles;
std::map<u32, ParticleSpawner*> all_particlespawners;

Particle::Particle(
	IGameDef *gamedef,
	scene::ISceneManager* smgr,
	LocalPlayer *player,
	ClientEnvironment &env,
	v3f pos,
	v3f velocity,
	v3f acceleration,
	float expirationtime,
	float size,
	bool collisiondetection,
	video::ITexture *texture,
	v2f texpos,
	v2f texsize
):
	scene::ISceneNode(smgr->getRootSceneNode(), smgr)
{
	// Misc
	m_gamedef = gamedef;

	// Texture
	m_material.setFlag(video::EMF_LIGHTING, false);
	m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
	m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
	m_material.setFlag(video::EMF_FOG_ENABLE, true);
	m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
	m_material.setTexture(0, texture);
	m_texpos = texpos;
	m_texsize = texsize;


	// Particle related
	m_pos = pos;
	m_velocity = velocity;
	m_acceleration = acceleration;
	m_expiration = expirationtime;
	m_time = 0;
	m_player = player;
	m_size = size;
	m_collisiondetection = collisiondetection;

	// Irrlicht stuff
	m_collisionbox = core::aabbox3d<f32>
			(-size/2,-size/2,-size/2,size/2,size/2,size/2);
	this->setAutomaticCulling(scene::EAC_OFF);

	// Init lighting
	updateLight(env);

	// Init model
	updateVertices();

	all_particles.push_back(this);
}

Particle::~Particle()
{
}

void Particle::OnRegisterSceneNode()
{
	if (IsVisible)
	{
		SceneManager->registerNodeForRendering
				(this, scene::ESNRP_TRANSPARENT);
		SceneManager->registerNodeForRendering
				(this, scene::ESNRP_SOLID);
	}

	ISceneNode::OnRegisterSceneNode();
}

void Particle::render()
{
	// TODO: Render particles in front of water and the selectionbox

	video::IVideoDriver* driver = SceneManager->getVideoDriver();
	driver->setMaterial(m_material);
	driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);

	u16 indices[] = {0,1,2, 2,3,0};
	driver->drawVertexPrimitiveList(m_vertices, 4,
			indices, 2, video::EVT_STANDARD,
			scene::EPT_TRIANGLES, video::EIT_16BIT);
}

void Particle::step(float dtime, ClientEnvironment &env)
{
	m_time += dtime;
	if (m_collisiondetection)
	{
		core::aabbox3d<f32> box = m_collisionbox;
		v3f p_pos = m_pos*BS;
		v3f p_velocity = m_velocity*BS;
		v3f p_acceleration = m_acceleration*BS;
		collisionMoveSimple(&env, m_gamedef,
			BS*0.5, box,
			0, dtime,
			p_pos, p_velocity, p_acceleration);
		m_pos = p_pos/BS;
		m_velocity = p_velocity/BS;
		m_acceleration = p_acceleration/BS;
	}
	else
	{
		m_velocity += m_acceleration * dtime;
		m_pos += m_velocity * dtime;
	}

	// Update lighting
	updateLight(env);

	// Update model
	updateVertices();
}

void Particle::updateLight(ClientEnvironment &env)
{
	u8 light = 0;
	try{
		v3s16 p = v3s16(
			floor(m_pos.X+0.5),
			floor(m_pos.Y+0.5),
			floor(m_pos.Z+0.5)
		);
		MapNode n = env.getClientMap().getNode(p);
		light = n.getLightBlend(env.getDayNightRatio(), m_gamedef->ndef());
	}
	catch(InvalidPositionException &e){
		light = blend_light(env.getDayNightRatio(), LIGHT_SUN, 0);
	}
	m_light = decode_light(light);
}

void Particle::updateVertices()
{
	video::SColor c(255, m_light, m_light, m_light);
	f32 tx0 = m_texpos.X;
	f32 tx1 = m_texpos.X + m_texsize.X;
	f32 ty0 = m_texpos.Y;
	f32 ty1 = m_texpos.Y + m_texsize.Y;

	m_vertices[0] = video::S3DVertex(-m_size/2,-m_size/2,0, 0,0,0,
			c, tx0, ty1);
	m_vertices[1] = video::S3DVertex(m_size/2,-m_size/2,0, 0,0,0,
			c, tx1, ty1);
	m_vertices[2] = video::S3DVertex(m_size/2,m_size/2,0, 0,0,0,
			c, tx1, ty0);
	m_vertices[3] = video::S3DVertex(-m_size/2,m_size/2,0, 0,0,0,
			c, tx0, ty0);

	for(u16 i=0; i<4; i++)
	{
		m_vertices[i].Pos.rotateYZBy(m_player->getPitch());
		m_vertices[i].Pos.rotateXZBy(m_player->getYaw());
		m_box.addInternalPoint(m_vertices[i].Pos);
		m_vertices[i].Pos += m_pos*BS;
	}
}


/*
	Helpers
*/


void allparticles_step (float dtime, ClientEnvironment &env)
{
	for(std::vector<Particle*>::iterator i = all_particles.begin();
			i != all_particles.end();)
	{
		if ((*i)->get_expired())
		{
			(*i)->remove();
			delete *i;
			all_particles.erase(i);
		}
		else
		{
			(*i)->step(dtime, env);
			i++;
		}
	}
}

void addDiggingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
		LocalPlayer *player, ClientEnvironment &env, v3s16 pos,
		const TileSpec tiles[])
{
	for (u16 j = 0; j < 32; j++) // set the amount of particles here
	{
		addNodeParticle(gamedef, smgr, player, env, pos, tiles);
	}
}

void addPunchingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
		LocalPlayer *player, ClientEnvironment &env,
		v3s16 pos, const TileSpec tiles[])
{
	addNodeParticle(gamedef, smgr, player, env, pos, tiles);
}

// add a particle of a node
// used by digging and punching particles
void addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr,
		LocalPlayer *player, ClientEnvironment &env, v3s16 pos,
		const TileSpec tiles[])
{
	// Texture
	u8 texid = myrand_range(0,5);
	video::ITexture *texture = tiles[texid].texture;

	// Only use first frame of animated texture
	f32 ymax = 1;
	if(tiles[texid].material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES)
		ymax /= tiles[texid].animation_frame_count;

	float size = rand()%64/512.;
	float visual_size = BS*size;
	v2f texsize(size*2, ymax*size*2);
	v2f texpos;
	texpos.X = ((rand()%64)/64.-texsize.X);
	texpos.Y = ymax*((rand()%64)/64.-texsize.Y);

	// Physics
	v3f velocity(	(rand()%100/50.-1)/1.5,
			rand()%100/35.,
			(rand()%100/50.-1)/1.5);

	v3f acceleration(0,-9,0);
	v3f particlepos = v3f(
		(f32)pos.X+rand()%100/200.-0.25,
		(f32)pos.Y+rand()%100/200.-0.25,
		(f32)pos.Z+rand()%100/200.-0.25
	);

	new Particle(
		gamedef,
		smgr,
		player,
		env,
		particlepos,
		velocity,
		acceleration,
		rand()%100/100., // expiration time
		visual_size,
		true,
		texture,
		texpos,
		texsize);
}

/*
	ParticleSpawner
*/

ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr, LocalPlayer *player,
	u16 amount, float time,
	v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
	float minexptime, float maxexptime, float minsize, float maxsize,
	bool collisiondetection, video::ITexture *texture, u32 id)
{
	m_gamedef = gamedef;
	m_smgr = smgr;
	m_player = player;
	m_amount = amount;
	m_spawntime = time;
	m_minpos = minpos;
	m_maxpos = maxpos;
	m_minvel = minvel;
	m_maxvel = maxvel;
	m_minacc = minacc;
	m_maxacc = maxacc;
	m_minexptime = minexptime;
	m_maxexptime = maxexptime;
	m_minsize = minsize;
	m_maxsize = maxsize;
	m_collisiondetection = collisiondetection;
	m_texture = texture;
	m_time = 0;

	for (u16 i = 0; i<=m_amount; i++)
	{
		float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
		m_spawntimes.push_back(spawntime);
	}

	all_particlespawners.insert(std::pair<u32, ParticleSpawner*>(id, this));
}

ParticleSpawner::~ParticleSpawner() {}

void ParticleSpawner::step(float dtime, ClientEnvironment &env)
{
	m_time += dtime;

	if (m_spawntime != 0) // Spawner exists for a predefined timespan
	{
		for(std::vector<float>::iterator i = m_spawntimes.begin();
				i != m_spawntimes.end();)
		{
			if ((*i) <= m_time && m_amount > 0)
			{
				m_amount--;

				v3f pos = random_v3f(m_minpos, m_maxpos);
				v3f vel = random_v3f(m_minvel, m_maxvel);
				v3f acc = random_v3f(m_minacc, m_maxacc);
				float exptime = rand()/(float)RAND_MAX
						*(m_maxexptime-m_minexptime)
						+m_minexptime;
				float size = rand()/(float)RAND_MAX
						*(m_maxsize-m_minsize)
						+m_minsize;

				new Particle(
					m_gamedef,
					m_smgr,
					m_player,
					env,
					pos,
					vel,
					acc,
					exptime,
					size,
					m_collisiondetection,
					m_texture,
					v2f(0.0, 0.0),
					v2f(1.0, 1.0));
				m_spawntimes.erase(i);
			}
			else
			{
				i++;
			}
		}
	}
	else // Spawner exists for an infinity timespan, spawn on a per-second base
	{
		for (int i = 0; i <= m_amount; i++)
		{
			if (rand()/(float)RAND_MAX < dtime)
			{
				v3f pos = random_v3f(m_minpos, m_maxpos);
				v3f vel = random_v3f(m_minvel, m_maxvel);
				v3f acc = random_v3f(m_minacc, m_maxacc);
				float exptime = rand()/(float)RAND_MAX
						*(m_maxexptime-m_minexptime)
						+m_minexptime;
				float size = rand()/(float)RAND_MAX
						*(m_maxsize-m_minsize)
						+m_minsize;

				new Particle(
					m_gamedef,
					m_smgr,
					m_player,