#[no_std, cache_output]
extract import "control_flow.spwn"
extract import "constants.spwn".comparisons
DEFAULT_BITS = 10
type @counter
impl @counter {
new: #[desc("Creates a new counter") example("
@counter::new() // creates a new counter with a starting value of 0
@counter::new(10) // creates a new counter with a starting value of 10
@counter::new(5i) // creates a new counter thaat uses item ID 5
@counter::new(true) // creates a new counter with a starting value of true (1)
")] (
#[desc("Source (can be a number, item ID or boolean)")] source: @number | @item | @bool = 0,
#[desc(
"Defines the maximum stable size of the counter. If the counter goes outside of the range from 0 to 2^size, it's behavior will be undefined. Smaller sizes are more group effective."
)] bits: @number = DEFAULT_BITS
){
if source.type == @number {
id = ?i
if source != 0 {
id.add(source)
}
-> return @counter::{
item: id,
bits
}
} else if source.type == @item {
-> return @counter::{
item: source,
bits
}
} else if source.type == @bool {
if bits != DEFAULT_BITS && bits != 1 {
throw "A boolean counter can only have size = 1"
}
id = ?i
if source {
id.add(1)
}
-> return @counter::{
item: id,
bits: 1,
}
}
}
}
impl @counter {
display:
#[desc("Creates a item display object that displays the value of the counter") example("
points = counter(0)
points.display(75, 75)
")]
(
self,
#[desc("X pos of display in units (1 grid square = 30 units)")] x: @number,
#[desc("Y pos of display in units")] y: @number
) {
extract import "constants.spwn".obj_props
$.add(obj {
OBJ_ID: 1615,
X: x,
Y: y,
ITEM: self.item,
COLOR: 1c,
GROUPS: 999g
});
},
add_to:
#[desc("Adds the counter's value to a counter (or all counters in a list), and resets the counter to 0 in the process") example("
a = counter(100)
b = counter(0)
wait(1)
a.add_to(b)
// a is now 0, b is now 100
")]
(
self,
#[desc("Counter(s) to add to")] items: [@counter | @item] | @counter | @item,
#[desc("Multiplier for the value added")] factor: @number = 1,
#[desc("Macro to be called for each decrease of the counter. Takes one argument representing the number the counter is being decreased by")] for_each: @macro = (n){}
) {
for i in self.bits..0 {
x = 2^i
-> if self >= x {
self -= x
if items.type == @array {
for t in items {
t.add(x * factor)
}
} else {
items.add(x * factor)
}
for_each(x)
}
}
},
add_to_multifactor:
#[desc("Like normal add_to, but each counter has its own factor")] (
self,
#[desc("Counter(s) to add to")] items: [[@counter | @number]],
) {
for i in self.bits..0 {
x = 2^i
-> if self >= x {
self -= x
for t in items {
t[0].add(x * t[1])
}
}
}
},
subtract_from:
#[desc("Subtracts the counter's value from another counter (or all counters in a list), and resets the counter to 0 in the process") example("
a = counter(100)
b = counter(70)
wait(1)
b.subtract_from(a)
// a is now 30, b is now 0
")]
(
self,
#[desc("Counter(s) to subtract from")] items: [@counter | @item] | @counter | @item,
#[desc("Multiplier for the value subtracted")] factor: @number = 1,
#[desc("Macro to be called for each decrease of the counter. Takes one argument representing the number the counter is being decreased by")] for_each: @macro = (n){}
) {
for i in self.bits..0 {
x = 2^i
-> if self >= x {
self -= x
if items.type == @array {
for t in items {
t.add(-x * factor)
}
} else {
items.add(-x * factor)
}
for_each(x)
}
}
},
multiply:
#[desc("Multiplies the value of the counter by some factor (does not consume the factor)") example("
c = counter(5)
wait(1)
c.multiply(10)
// c is now 50
")]
(
self,
#[desc("Factor to multiply by, either another counter (very expensive) or a normal number")] factor: @counter | @number,
) {
if factor.type == @number {
temp = @counter::new(0, bits = self.bits)
self.add_to([temp.item], factor)
temp.add_to([self.item])
} else if factor.type == @counter {
wait()
result = @counter::new(0, bits = self.bits)
for i in self.bits..0 {
x = 2^i
-> if self >= x {
self -= x
factor.copy_to(result, factor = x)
}
}
result.add_to(self)
}
},
divide: #[desc("Devides the value of the counter by some divisor") example("
c = counter(7)
r = counter(0)
wait(1)
c.divide(2, remainder = r)
// c is now 3, r is now 1
")]
(
self,
#[desc("Divisor to divide by, either another counter (very expensive) or a normal number")] divisor: @counter | @number,
#[desc("Counter or item to set to the remainder value")] remainder: @counter | @item = @counter::new(),
) {
if divisor.type == @number {
result = @counter::new(0, bits = self.bits)
for i in self.bits..0 {
num = 2^i
-> if self >= divisor * num {
self.add(-divisor * num)
result.add(num)
}
}
self.add_to(remainder)
result.add_to(self)
} else if divisor.type == @counter {
wait()
result = @counter::new(0, bits = self.bits)
broken = @counter::new(false)
if divisor.bits > 8 {
throw "Divisor max size (8 bits) exceeded"
}
-> if broken == 1 {
broken -= 1
}
for i in self.bits..0 {
-> if broken == 0 {
num = 2^i
self.item.if_is(LARGER_THAN, 0, !{
divisor.copy_to(self, factor = -num)
result.add(num)
})
-> if self == 0 {
broken += 1
}
-> if self < 0 {
divisor.copy_to(self, factor = num)
result.add(-num)
}
}
}
-> if broken == 0 {
-> if self < 0 {
divisor.copy_to(self)
result.add(-1)
broken += 1
}
}
self.add_to(remainder)
result.add_to(self)
}
},
//will consume both numbers
compare: #[desc("Returns 0 if both counters are equal, 1 if the other is smaller, and -1 if the other is greater.") example("
c1 = counter(10)
c2 = counter(15)
cmp = c1.compare(c2) // -1
// c1 is now -5, c2 is now 0
")](self, other: @counter, factor: @number = 1) {
comp = @counter::new(0, bits = $.max(self.bits, other.bits))
self.copy_to(comp)
other.copy_to(comp, factor = -1)
comp.item.if_is(EQUAL_TO, 0, !{
-> return 0
})
comp.item.if_is(LARGER_THAN, 0, !{
comp.reset()
-> return 1
})
comp.item.if_is(SMALLER_THAN, 0, !{
comp.reset_negative()
-> return -1
})
},
reset: #[desc("Resets counter to 0.") example("
c = counter(100)
wait(1)
c.reset()
// c is now 0
")]
(
self,
#[desc("Macro to be called for each decrease of the counter. Takes one argument representing the number the counter is being decreased by")] for_each: @macro = (n){}
){
for i in self.bits..0 {
x = 2^i
-> if self >= x {
self -= x
for_each(x)
}
}
},
reset_negative: #[desc("Resets a negative counter to 0") example("
c = counter(-100)
wait(1)
c.reset_negative()
// c is now 0
")]
(
self,
#[desc("Macro to be called for each decrease of the counter. Takes one argument representing the number the counter is being decreased by")] for_each: @macro = (n){}
){
for i in self.bits..0 {
x = 2^i
-> if self <= -x {
self += x
for_each(x)
}
}
},
copy_to: #[desc("Copies the value of the counter to another counter (or to all counters in a list), without consuming the original") example("
c1 = counter(100)
c2 = counter(0)
wait(1)
c1.copy_to(c2)
// both counters are now 100
")]
(
self,
#[desc("Counter(s) to copy to")] items: [@counter | @item] | @counter | @item,
#[desc("Factor of to multiply the copy by")] factor: @number = 1
) {
temp_storage = @counter::new(0, bits = self.bits)
for i in self.bits..0 {
x = 2^i
-> if self >= x {
self -= x
temp_storage += x
if items.type == @array {
for t in items {
t.add(x * factor)
}
} else {
items.add(x * factor)
}
}
}
temp_storage.add_to(self)
},
clone: #[desc("Copies the counter and returns the copy") example("
c1 = counter(100)
c2 = c1.clone()
// c1 and c2 are now 100
")] (
self
) {
new_counter = @counter::new(0, bits = self.bits)
self.copy_to([new_counter])
return new_counter
},
_plus_: #[desc("Implementation of the plus (`+`) operator") example("
c = counter(10)
c2 = c1 + 10
// c2 is 20
")]
(self, other: @number | @counter) {
if other.type == @number {
new_counter = self.clone()
new_counter.add(other)
-> return new_counter
} else if other.type == @counter {
new_counter = self.clone()
other.copy_to([new_counter.item], factor = 1)
-> return new_counter
}
},
_minus_: #[desc("Implementation of the minus (`-`) operator") example("
c = counter(10)
c2 = c1 - 3
// c2 is 7
")]
(self, other: @number | @counter) {
if other.type == @number {
new_counter = self.clone()
new_counter.add(-other)
return new_counter
} else if other.type == @counter {
new_counter = self.clone()
other.copy_to([new_counter.item], factor = -1)
return new_counter
}
},
_times_: #[desc("Implementation of the times (`*`) operator") example("
c = counter(10)
c2 = c1 * 10
// c2 is 100
")]
(self, num: @number | @counter) {
if other.type == @number {
new = @counter::new(0, self.bits)
self.copy_to(new, factor = other)
return new
} else if other.type == @counter {
new = self.clone()
new.multiply(other)
return new
}
},
_divided_by_: #[desc("Implementation of the divided by (`/`) operator") example("
c = counter(100)
c2 = c1 / 10
// c2 is 10
")]
(self, num: @number | @counter) {
clone = self.clone()
clone.divide(num)
-> return clone
},
_mod_: #[desc("Implementation of the modulus (`%`) operator") example("
c = counter(42)
c2 = c1 % 10
// c2 is 2
")]
(self, num: @number | @counter) {
clone = self.clone()
out = @counter::new()
clone.divide(num, remainder = out)
-> return out
},
_modulate_: #[desc("Implementation of the modulate (`%=`) operator") example("
c = counter(42)
c %= 10
// c is 2
")]
(self, num: @number | @counter) {
out = @counter::new()
self.divide(num, remainder = out)
self.reset()
out.add_to([self])
},
_more_than_: #[desc("Implementation of the more than (`>`) operator") example("
c = counter(42)
more = c > 10
// more is now true
")]
(self, other: @number | @counter) {
if other.type == @number {
self.item.if_is(LARGER_THAN, other, !{
-> return true
})
self.item.if_is(SMALLER_THAN, other + 1, !{
-> return false
})
} else if other.type == @counter {
cmp = self.compare(other)
return cmp == 1
}
},
_less_than_: #[desc("Implementation of the less than (`<`) operator") example("
c = counter(42)
less = c < 42
// less is now false
")]
(self, other: @number | @counter) {
if other.type == @number {
self.item.if_is(SMALLER_THAN, other, !{
-> return true
})
self.item.if_is(LARGER_THAN, other - 1, !{
-> return false
})
} else if other.type == @counter {
cmp = self.compare(other)
-> return cmp == -1
}
},
_more_or_equal_: #[desc("Implementation of the more than or equal (`>=`) operator") example("
c = counter(42)
more_or_eq = c >= 10
// more_or_eq is now true
")]
(self, other: @number | @counter) {
if other.type == @number {
self.item.if_is(LARGER_THAN, other - 1, !{
-> return true
})
self.item.if_is(SMALLER_THAN, other, !{
-> return false
})
} else if other.type == @counter {
cmp = self.compare(other)
-> return cmp == 1 || cmp == 0
}
},
_less_or_equal_: #[desc("Implementation of the less than or equal (`<=`) operator") example("
c = counter(42)
less_or_eq = c <= 42
// less_or_eq is now true
")]
(self, other: @number | @counter) {
if other.type == @number {
self.item.if_is(SMALLER_THAN, other + 1, !{
-> return true
})
self.item.if_is(LARGER_THAN, other, !{
-> return false
})
} else if other.type == @counter {
cmp = self.compare(other)
-> return cmp == -1 || cmp == 0
}
},
_equal_: #[desc("Implementation of the equals (`==`) operator") example("
c = counter(42)
eq = c == 42
// eq is now true
")]
(self, other: @number | @counter) {
if other.type == @number {
self.item.if_is(EQUAL_TO, other, !{
-> return true
})
ret_false = !{
-> return false
}
self.item.if_is(LARGER_THAN, other, ret_false)
self.item.if_is(SMALLER_THAN, other, ret_false)
} else if other.type == @counter {
cmp = self.compare(other)
-> return cmp == 0
}
},
_not_equal_: #[desc("Implementation of the not equal (`!=`) operator") example("
c = counter(42)
not_eq = c != 42
// not_eq is now false
")](self, other: @number | @counter) {
-> return !(self == other)
},
add: #[desc("Implementation of the pickup trigger") example("
c = counter(10)
c.add(10)
// c is now 20
")]
(self, #[desc("Amount to add")] num: @number) {
self.item.add(num)
},
_add_: #[desc("Implementation of the add (`+=`) operator") example("
c = counter(10)
c += 10
// c is now 20
")](self, num: @number | @counter) {
if num.type == @number {
self.add(num)
} else if num.type == @counter {
num.copy_to(self) //holy shit why was this not like this all along
}
},
_increment_: #[desc("Implementation of the increment (`n++`) operator. Does not return any value.") example("
c = counter(10)
c++
// c is now 11
")](self) {
self.add(1)
},
_decrement_: #[desc("Implementation of the decrement (`n--`) operator. Does not return any value.") example("
c = counter(10)
c--
// c is now 9
")](self) {
self.add(-1)
},
_subtract_: #[desc("Implementation of the subtract (`-=`) operator") example("
c = counter(20)
c -= 5
// c is now 15
")](self, num: @number | @counter) {
if num.type == @number {
self.add(-num)
} else if num.type == @counter {
num.copy_to(self, factor = -1)
}
},
_multiply_: #[desc("Implementation of the multiply (`*=`) operator") example("
c = counter(5)
c *= 6
// c is now 30
")](self, num: @number | @counter) {
if num.type == @number {
self.multiply(num)
} else if num.type == @counter {
self.multiply(num)
}
},
_divide_: #[desc("Implementation of the divide (`/=`) operator") example("
c = counter(30)
c /= 6
// c is now 5
")](self, num: @number | @counter) {
if num.type == @number {
self.divide(num)
} else if num.type == @counter {
self.divide(num)
}
},
_assign_: #[desc("Implementation of the assign (`=`) operator") example("
c = counter(23)
c = 42
// c is now 42
")](self, num: @number | @counter) {
self.reset()
if num.type == @number {
if num > 0 {
self.add(num)
}
} else if num.type == @counter {
num.copy_to(self)
}
},
_swap_: #[desc("Implementation of the swap (`<=>`) operator") example("
c = counter(23)
c2 = counter(42)
wait(1)
c <=> c2
// c is now 42, c2 is now 23
")]
(self, num: @counter) {
swap_tmp = @counter::new();
self.add_to(swap_tmp)
num.add_to(self)
swap_tmp.add_to(num)
},
to_const: #[desc("Converts the counter into a normal number (very context-splitting, be careful)") example("
c = counter(3)
wait(1)
10g.move(c.to_const(0..10) * 10, 0, 1)
// group ID 10 moves 3 blocks
")] (
self,
#[desc("Array or range of possible output values")] range: [@number] | @range
) {
for val in range {
-> self.item.if_is(EQUAL_TO, val, !{
-> return val
})
}
},
to_const_enclosed: #[desc("Converts the counter into a normal number that you can use within a macro") example("
c = counter(3)
wait(1)
c.to_const_enclosed(0..10, (c_const) {
10g.move(c_const * 10, 0, 1)
})
// group ID 10 moves 3 blocks
")] (
self,
#[desc("Array or range of possible output values")] range: [@number] | @range,
#[desc("Closure where you can use the const value, should take the value as the first argument")] closure: @macro,
) {
for val in range {
-> self.item.if_is(EQUAL_TO, val, !{
closure(val)
})
}
},
_as_: #[desc("Implementation of the as (`as`) operator") example("
c = counter(1)
b = c as @bool
// b is now true
")]
(self, _type: @type_indicator) {
if _type == @bool {
-> return self != 0
} else {
throw "Cannot convert counter to " + _type as @string + " (counter can convert to a number using the counter.to_const macro)"
}
},
reaches: #[desc("Returns an event for when the counter reaches a certain value")
example("
c = counter(2)
on(c.reaches(10), !{
BG.pulse(0, 255, 0, fade_out = 0.5) // will pulse each time the counter becomes 10
})
")] (self, #[desc("Value to reach")] value: @number) {
return self.item.count(value)
}
}