1 /**
2 Copyright: Copyright (c) 2016-2018 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module railroad.plugin;
7 
8 import datadriven;
9 import pluginlib;
10 import voxelman.core.config;
11 import voxelman.core.events;
12 import voxelman.core.packets;
13 import voxelman.log;
14 
15 import voxelman.blockentity.blockentityman;
16 import voxelman.command.plugin;
17 import voxelman.blockentity.plugin;
18 import voxelman.edit.plugin;
19 import voxelman.entity.plugin;
20 import voxelman.eventdispatcher.plugin;
21 import voxelman.graphics.plugin;
22 import voxelman.net.plugin;
23 import voxelman.world.clientworld;
24 import voxelman.world.serverworld;
25 import voxelman.world.storage;
26 import voxelman.worldinteraction.plugin;
27 
28 import voxelman.world.block;
29 import voxelman.math;
30 import voxelman.geometry;
31 
32 import voxelman.world.blockentity;
33 
34 import railroad.rail.mesh;
35 import railroad.rail.utils;
36 import railroad.rail.railtool;
37 import railroad.rail.railgraph;
38 import railroad.rail.packets;
39 
40 import railroad.wagon.wagontool;
41 import railroad.wagon.packets;
42 import railroad.wagon.wagon;
43 
44 
45 final class RailroadPluginClient : IPlugin
46 {
47 	mixin IdAndSemverFrom!"railroad.plugininfo";
48 	mixin RailroadPluginCommon;
49 
50 	ClientWorld clientWorld;
51 	NetClientPlugin connection;
52 	WorldInteractionPlugin worldInteraction;
53 	GraphicsPlugin graphics;
54 
55 	Batch batch;
56 	EntityManager* eman;
57 
58 	override void registerResources(IResourceManagerRegistry resmanRegistry) {
59 		retreiveBlockEntityManager(resmanRegistry);
60 		auto components = resmanRegistry.getResourceManager!EntityComponentRegistry;
61 		eman = components.eman;
62 		eman.registerComponent!WagonClientComponent();
63 	}
64 
65 	override void preInit() {
66 		import voxelman.globalconfig;
67 		import voxelman.model.obj;
68 		import voxelman.model.ply;
69 		import voxelman.model.mesh;
70 		import voxelman.model.utils;
71 		import voxelman.world.mesh.chunkmesh;
72 		try{
73 			railMeshes[0] = readPlyFile!RailVertexT(BUILD_TO_ROOT_PATH~"res/model/rail1.ply");
74 			railMeshes[1] = readPlyFile!RailVertexT(BUILD_TO_ROOT_PATH~"res/model/rail2.ply");
75 			railMeshes[2] = readPlyFile!RailVertexT(BUILD_TO_ROOT_PATH~"res/model/rail3.ply");
76 		}
77 		catch(Exception e){
78 			warningf("Error reading model, %s", e);
79 		}
80 	}
81 
82 	override void init(IPluginManager pluginman)
83 	{
84 		worldInteraction = pluginman.getPlugin!WorldInteractionPlugin;
85 		clientWorld = pluginman.getPlugin!ClientWorld;
86 		graphics = pluginman.getPlugin!GraphicsPlugin;
87 
88 		connection = pluginman.getPlugin!NetClientPlugin;
89 		connection.registerPacket!PlaceRailPacket;
90 		connection.registerPacket!EditRailLinePacket;
91 		connection.registerPacket!CreateWagonPacket;
92 
93 		auto railTool = new RailTool(clientWorld, blockEntityManager,
94 			connection, worldInteraction);
95 
96 		auto wagonTool = new WagonTool(clientWorld, blockEntityManager,
97 			connection, worldInteraction, graphics);
98 
99 		auto editPlugin = pluginman.getPlugin!EditPlugin;
100 		editPlugin.registerTool(railTool);
101 		editPlugin.registerTool(wagonTool);
102 
103 		auto evDispatcher = pluginman.getPlugin!EventDispatcherPlugin;
104 		evDispatcher.subscribeToEvent(&drawEntities);
105 	}
106 
107 	void drawEntities(ref RenderSolid3dEvent event)
108 	{
109 		batch.reset();
110 		auto query = eman.query!WagonClientComponent;
111 		foreach (id, wagonClient; query)
112 		{
113 			if (wagonClient.dimension == clientWorld.currentDimension)
114 			{
115 				batch.putCube(wagonClient.dimPos - vec3(0.5,0.5,0.5), vec3(1,1,1), Colors.black, true);
116 			}
117 		}
118 		graphics.draw(batch);
119 	}
120 }
121 
122 final class RailroadPluginServer : IPlugin
123 {
124 	mixin IdAndSemverFrom!"railroad.plugininfo";
125 	mixin RailroadPluginCommon;
126 
127 	WagonLogicServer wagonLogic;
128 
129 	NetServerPlugin connection;
130 	ServerWorld serverWorld;
131 	BlockEntityServer blockEntityPlugin;
132 	RailGraph railGraph;
133 
134 	override void registerResources(IResourceManagerRegistry resmanRegistry)
135 	{
136 		wagonLogic.registerResources(resmanRegistry);
137 		auto ioman = resmanRegistry.getResourceManager!IoManager;
138 		ioman.registerWorldLoadSaveHandlers(&railGraph.read, &railGraph.write);
139 		retreiveBlockEntityManager(resmanRegistry);
140 	}
141 
142 	override void init(IPluginManager pluginman)
143 	{
144 		connection = pluginman.getPlugin!NetServerPlugin;
145 		connection.registerPacket!PlaceRailPacket(&handlePlaceRailPacket);
146 		connection.registerPacket!EditRailLinePacket(&handleEditRailLinePacket);
147 		connection.registerPacket!CreateWagonPacket(&wagonLogic.handleCreateWagonPacket);
148 
149 		serverWorld = pluginman.getPlugin!ServerWorld;
150 		blockEntityPlugin = pluginman.getPlugin!BlockEntityServer;
151 
152 		wagonLogic.entityPlugin = pluginman.getPlugin!EntityPluginServer;
153 		wagonLogic.railGraph = &railGraph;
154 
155 		auto evDispatcher = pluginman.getPlugin!EventDispatcherPlugin;
156 		evDispatcher.subscribeToEvent(&wagonLogic.process);
157 
158 		auto command = pluginman.getPlugin!CommandPluginServer;
159 		command.registerCommand(CommandInfo("remove_wagons", &handleRemoveWagons, null, "Removes all wagons from the world"));
160 	}
161 
162 	private void handleRemoveWagons(CommandParams params)
163 	{
164 		wagonLogic.removeWagons;
165 	}
166 
167 	void handlePlaceRailPacket(ubyte[] packetData, SessionId sessionId)
168 	{
169 		auto packet = unpackPacket!PlaceRailPacket(packetData);
170 		RailPos railPos = packet.pos;
171 		RailData railData = RailData(packet.data);
172 		editRail(railPos, railData);
173 	}
174 
175 	void handleEditRailLinePacket(ubyte[] packetData, SessionId sessionId)
176 	{
177 		auto packet = unpackPacket!EditRailLinePacket(packetData);
178 		final switch(packet.orientation) {
179 			case RailOrientation.x:
180 				RailData railData = RailData(RailSegment.xpos);
181 				foreach(dx; 0..packet.length)
182 				{
183 					RailPos railPos = packet.from;
184 					railPos.x += dx;
185 					editRail(railPos, railData, packet.editOp);
186 				}
187 				break;
188 			case RailOrientation.z:
189 				RailData railData = RailData(RailSegment.zneg);
190 				foreach(dz; 0..packet.length)
191 				{
192 					RailPos railPos = packet.from;
193 					railPos.z += dz;
194 					editRail(railPos, railData, packet.editOp);
195 				}
196 				break;
197 			case RailOrientation.xzSameSign:
198 				bool topSide = packet.diagonalRailSide == DiagonalRailSide.zneg;
199 				RailSegment[2] sides = [RailSegment.xposZpos, RailSegment.xnegZneg];
200 				ivec2[2] increments = [ivec2(1, 0), ivec2(0, -1)];
201 				placeDiagonalRail(topSide, packet.from, sides, increments, packet.length, packet.editOp);
202 				break;
203 			case RailOrientation.xzOppSign:
204 				bool topSide = packet.diagonalRailSide == DiagonalRailSide.zneg;
205 				RailSegment[2] sides = [RailSegment.xnegZpos, RailSegment.xposZneg];
206 				ivec2[2] increments = [ivec2(0, 1), ivec2(1, 0)];
207 				placeDiagonalRail(topSide, packet.from, sides, increments, packet.length, packet.editOp);
208 				break;
209 		}
210 	}
211 
212 	private void placeDiagonalRail(bool topSide, RailPos railPos, RailSegment[2] sides, ivec2[2] increments, size_t length, RailEditOp editOp)
213 	{
214 		foreach(i; 0..length)
215 		{
216 			RailData railData = RailData(sides[cast(size_t)topSide]);
217 			editRail(railPos, railData, editOp);
218 
219 			auto inc = increments[cast(size_t)topSide];
220 			railPos.x += inc.x;
221 			railPos.z += inc.y;
222 			topSide = !topSide;
223 		}
224 	}
225 
226 	void editRail(const RailPos railPos, const RailData railData, RailEditOp editOp = RailEditOp.add)
227 	{
228 		ChunkWorldPos cwp = railPos.chunkPos();
229 		ushort railEntityId = blockEntityManager.getId("rail");
230 
231 		RailData railOnGround = getRailAt(railPos, railEntityId,
232 			serverWorld.worldAccess, serverWorld.entityAccess);
233 
234 		RailData edited = railOnGround;
235 		edited.editRail(railData, editOp);
236 
237 		// Adding existing rail segment / removing non-existing
238 		if (railOnGround == edited) return;
239 
240 		railGraph.onRailEdit(railPos, railData, editOp);
241 
242 		if (!railOnGround.empty)
243 		{
244 			BlockWorldPos delPos = railPos.deletePos;
245 			WorldBox changedBox = removeEntity(delPos, blockEntityPlugin.blockEntityInfos,
246 				serverWorld.worldAccess, serverWorld.entityAccess, BlockId(1));
247 			connection.sendTo(serverWorld.chunkObserverManager.getChunkObservers(cwp),
248 				RemoveBlockEntityPacket(delPos.vector));
249 		}
250 
251 		if (edited.empty) return;
252 
253 		WorldBox blockBox = edited.boundingBox(railPos);
254 		ulong payload = payloadFromIdAndEntityData(railEntityId, edited.data);
255 
256 		placeEntity(blockBox, payload, serverWorld.worldAccess, serverWorld.entityAccess);
257 
258 		connection.sendTo(serverWorld.chunkObserverManager.getChunkObservers(cwp),
259 			PlaceBlockEntityPacket(blockBox, payload));
260 	}
261 }
262 
263 mixin template RailroadPluginCommon()
264 {
265 	BlockEntityManager blockEntityManager;
266 	void retreiveBlockEntityManager(IResourceManagerRegistry resmanRegistry)
267 	{
268 		blockEntityManager = resmanRegistry.getResourceManager!BlockEntityManager;
269 		blockEntityManager.regBlockEntity("rail")
270 			.boxHandler(&railBoxHandler)
271 			.meshHandler(&makeRailMesh)
272 			.color([128, 128, 128])
273 			.blockShapeHandler(&railBlockShapeHandler)
274 			.sideSolidity(&railSideSolidity)
275 			.debugHandler(&railDebugHandler);
276 	}
277 }
278 
279 WorldBox railBoxHandler(BlockWorldPos bwp, BlockEntityData data)
280 {
281 	return RailData(data).boundingBox(bwp);
282 }
283 
284 Solidity railSideSolidity(CubeSide side, ivec3 chunkPos, ivec3 entityPos, BlockEntityData data)
285 {
286 	if (side == CubeSide.yneg)
287 	{
288 		return RailData(data).bottomSolidity(calcBlockTilePos(chunkPos));
289 	}
290 	return Solidity.transparent;
291 }
292 
293 BlockShape railBlockShapeHandler(ivec3 chunkPos, ivec3 entityPos, BlockEntityData data)
294 {
295 	auto railData = RailData(data);
296 	if (railData.bottomSolidity(calcBlockTilePos(chunkPos)))
297 	{
298 		if (railData.isSlope)
299 			return railSlopeShapes[railData.data - SLOPE_RAIL_BIT];
300 		else
301 			return railBlockShape;
302 	}
303 	else
304 		return emptyShape;
305 }
306 
307 const ShapeSideMask[6] railShapeSides = [
308 	ShapeSideMask.empty,
309 	ShapeSideMask.empty,
310 	ShapeSideMask.empty,
311 	ShapeSideMask.empty,
312 	ShapeSideMask.empty,
313 	ShapeSideMask.full]; // bottom is full
314 
315 const BlockShape railBlockShape = BlockShape(railShapeSides, 0b_0000_1111, true, true);
316 const BlockShape[4] railSlopeShapes = [
317 	BlockShape([ // zneg
318 		ShapeSideMask.full, ShapeSideMask.empty, ShapeSideMask.empty,
319 		ShapeSideMask.empty, ShapeSideMask.empty, ShapeSideMask.full],
320 		0b_0011_1111, true, true),
321 	BlockShape([ // xneg
322 		ShapeSideMask.empty, ShapeSideMask.empty, ShapeSideMask.empty,
323 		ShapeSideMask.full, ShapeSideMask.empty, ShapeSideMask.full],
324 		0b_0101_1111, true, true),
325 	BlockShape([ // zpos
326 		ShapeSideMask.empty, ShapeSideMask.full, ShapeSideMask.empty,
327 		ShapeSideMask.empty, ShapeSideMask.empty, ShapeSideMask.full],
328 		0b_1100_1111, true, true),
329 	BlockShape([ // xpos
330 		ShapeSideMask.empty, ShapeSideMask.empty, ShapeSideMask.full,
331 		ShapeSideMask.empty, ShapeSideMask.empty, ShapeSideMask.full],
332 		0b_1010_1111, true, true)];