summaryrefslogtreecommitdiffstats
path: root/src/Simulator/IncrementalRedstoneSimulator/RedstoneRepeaterHandler.h
blob: 8f5e8c1b7f8f50552f8fa1e7aaf300d51447a5b0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155

#pragma once

#include "../../Blocks/BlockRedstoneRepeater.h"





namespace RedstoneRepeaterHandler
{
	static bool IsOn(BLOCKTYPE a_Block)
	{
		return (a_Block == E_BLOCK_REDSTONE_REPEATER_ON);
	}

	/** Returns a pair with first element indicating if the block at the given position is an activated repeater.
	If it is activated, the second element is the repeater metadata. */
	static std::pair<bool, NIBBLETYPE> IsOnRepeater(cChunk & Chunk, const Vector3i a_Position)
	{
		BLOCKTYPE Type;
		NIBBLETYPE Meta;

		if (!Chunk.UnboundedRelGetBlock(a_Position, Type, Meta))
		{
			return std::make_pair(false, static_cast<NIBBLETYPE>(0));
		}

		return std::make_pair(IsOn(Type), Meta);
	}

	/** Determine, from the metadata of a repeater on our left side, if they lock us.
	To test a repeater on our right, simply invert the order of arguments provided.
	"Left" is relative to the direction the repeater output faces, naturally. */
	static bool DoesLhsLockMe(NIBBLETYPE a_MetaLhs, NIBBLETYPE a_MyMeta)
	{
		// Get the direction bits
		a_MetaLhs &= E_META_REDSTONE_REPEATER_FACING_MASK;
		a_MyMeta &= E_META_REDSTONE_REPEATER_FACING_MASK;

		/*
		Check for a valid locking configuration, where they are perpendicular and one snuggles into the other.

		Order of comparisons:
			XP >^ ZM
			ZP |_ XP
			XM <| ZP
			ZP ^< xM

		Key:
			^ Facing up
			_ Facing right
			| Facing down
			< Facing left
		*/
		return
			((a_MetaLhs == E_META_REDSTONE_REPEATER_FACING_XP) && (a_MyMeta == E_META_REDSTONE_REPEATER_FACING_ZM)) ||
			((a_MetaLhs == E_META_REDSTONE_REPEATER_FACING_ZP) && (a_MyMeta == E_META_REDSTONE_REPEATER_FACING_XP)) ||
			((a_MetaLhs == E_META_REDSTONE_REPEATER_FACING_XM) && (a_MyMeta == E_META_REDSTONE_REPEATER_FACING_ZP)) ||
			((a_MetaLhs == E_META_REDSTONE_REPEATER_FACING_ZM) && (a_MyMeta == E_META_REDSTONE_REPEATER_FACING_XM))
		;
	}

	/** Determine if a repeater is locked.
	A locked repeater is one with another powered repeater facing them, to their immediate left or right sides.
	"Left" is relative to the direction the repeater output faces, naturally. */
	static bool IsLocked(cChunk & Chunk, const Vector3i a_Position, const NIBBLETYPE a_Meta)
	{
		// The left hand side offset. Will be negated to get the rhs offset
		const auto LhsOffset = cBlockRedstoneRepeaterHandler::GetLeftCoordinateOffset(a_Meta);

		// Test the block to the left of us
		const auto Lhs = IsOnRepeater(Chunk, LhsOffset + a_Position);
		if (Lhs.first && DoesLhsLockMe(Lhs.second, a_Meta))
		{
			return true;
		}

		// Test the right side, flipping the argument order to DoesLhsLockMe
		const auto Rhs = IsOnRepeater(Chunk, -LhsOffset + a_Position);
		return Rhs.first && DoesLhsLockMe(a_Meta, Rhs.second);
	}

	static PowerLevel GetPowerDeliveredToPosition(const cChunk & a_Chunk, Vector3i a_Position, BLOCKTYPE a_BlockType, Vector3i a_QueryPosition, BLOCKTYPE a_QueryBlockType, bool IsLinked)
	{
		if (!IsOn(a_BlockType))
		{
			return 0;
		}

		const auto FrontOffset = cBlockRedstoneRepeaterHandler::GetFrontCoordinateOffset(a_Chunk.GetMeta(a_Position));
		const auto FrontPosition = a_Position + FrontOffset;
		if (a_QueryPosition == FrontPosition)
		{
			return 15;
		}

		return 0;
	}

	static void Update(cChunk & a_Chunk, cChunk & CurrentlyTicking, Vector3i a_Position, BLOCKTYPE a_BlockType, NIBBLETYPE a_Meta, const PowerLevel Power)
	{
		// LOGD("Evaluating loopy the repeater (%d %d %d)", a_Position.x, a_Position.y, a_Position.z);

		auto & Data = DataForChunk(a_Chunk);
		const auto DelayInfo = Data.GetMechanismDelayInfo(a_Position);

		// If the repeater is locked by another, ignore and forget all power changes:
		if (IsLocked(a_Chunk, a_Position, a_Meta))
		{
			if (DelayInfo != nullptr)
			{
				Data.m_MechanismDelays.erase(a_Position);
			}

			return;
		}

		if (DelayInfo == nullptr)
		{
			bool ShouldBeOn = (Power != 0);
			if (ShouldBeOn != IsOn(a_BlockType))
			{
				Data.m_MechanismDelays[a_Position] = std::make_pair((((a_Meta & 0xC) >> 0x2) + 1), ShouldBeOn);
			}

			return;
		}

		int DelayTicks;
		bool ShouldPowerOn;
		std::tie(DelayTicks, ShouldPowerOn) = *DelayInfo;

		if (DelayTicks != 0)
		{
			return;
		}

		const auto NewType = ShouldPowerOn ? E_BLOCK_REDSTONE_REPEATER_ON : E_BLOCK_REDSTONE_REPEATER_OFF;
		a_Chunk.FastSetBlock(a_Position, NewType, a_Meta);
		Data.m_MechanismDelays.erase(a_Position);

		// While sleeping, we ignore any power changes and apply our saved ShouldBeOn when sleep expires
		// Now, we need to recalculate to be aware of any new changes that may e.g. cause a new output change
		// FastSetBlock doesn't wake simulators, so manually update ourselves:
		Update(a_Chunk, CurrentlyTicking, a_Position, NewType, a_Meta, Power);

		UpdateAdjustedRelative(a_Chunk, CurrentlyTicking, a_Position, cBlockRedstoneRepeaterHandler::GetFrontCoordinateOffset(a_Meta));
	}

	static void ForValidSourcePositions(const cChunk & a_Chunk, Vector3i a_Position, BLOCKTYPE a_BlockType, NIBBLETYPE a_Meta, ForEachSourceCallback & Callback)
	{
		Callback(cBlockRedstoneRepeaterHandler::GetRearCoordinateOffset(a_Meta) + a_Position);
	}
};