Add electrical bullets

This commit is contained in:
2022-03-22 21:45:08 +00:00
parent bda65968b0
commit f030d8264f
10 changed files with 202 additions and 57 deletions
+3 -1
View File
@@ -3,6 +3,7 @@ module Dodge.Combine.Combinations where
import Geometry import Geometry
import Dodge.Data import Dodge.Data
import Dodge.WorldEvent.HitEffect import Dodge.WorldEvent.HitEffect
import Dodge.WorldEvent.SpawnParticle
import Dodge.Particle.Bullet.HitEffect import Dodge.Particle.Bullet.HitEffect
import Dodge.Item.Equipment import Dodge.Item.Equipment
import Dodge.Item.Craftable import Dodge.Item.Craftable
@@ -115,7 +116,8 @@ moduleCombinations =
, ( ModBullet , ( ModBullet
, bulletWeapons , bulletWeapons
, [amod [INCENDIARYMODULE] "+INCENDIARY" (f $ destroyOnImpact bulIncCr bulIncWall) , [amod [INCENDIARYMODULE] "+INCENDIARY" (f $ destroyOnImpact bulIncCr bulIncWall)
,amod [BOUNCEMODULE] "+BOUNCE" (f $ destroyOnImpact bulBounceArmCr' bulBounceWall) ,amod [BOUNCEMODULE] "+BOUNCE" (f $ destroyOnImpact bulBounceArmCr bulBounceWall)
,amod [STATICMODULE] "+STATIC" (f $ bulletDestroySpawn aStaticBall)
] ]
) )
, ( ModTarget , ( ModTarget
+3
View File
@@ -110,6 +110,9 @@ data CombineType
-- Modules -- Modules
-- bullet -- bullet
| INCENDIARYMODULE | INCENDIARYMODULE
| STATICMODULE
| FRAGMODULE
| FLASHMODULE
| BOUNCEMODULE | BOUNCEMODULE
-- --
| TELEPORTMODULE | TELEPORTMODULE
+5
View File
@@ -21,6 +21,11 @@ makeTypeCraftNum i ct = defaultCraftable
makeTypeCraft :: CombineType -> Item makeTypeCraft :: CombineType -> Item
makeTypeCraft = makeTypeCraftNum 1 makeTypeCraft = makeTypeCraftNum 1
makeModule :: CombineType -> Item
makeModule ct = makeTypeCraft ct
& itInvSize .~ 1
& itInvColor .~ chartreuse
incendiaryModule :: Item incendiaryModule :: Item
incendiaryModule = makeTypeCraft INCENDIARYMODULE incendiaryModule = makeTypeCraft INCENDIARYMODULE
& itInvSize .~ 1 & itInvSize .~ 1
+10
View File
@@ -1,6 +1,7 @@
module Dodge.Item.Weapon.Bullet module Dodge.Item.Weapon.Bullet
( basicBullet ( basicBullet
, incBullet , incBullet
, staticBullet
, conBullet , conBullet
, bounceBullet , bounceBullet
, ltBullet , ltBullet
@@ -8,6 +9,7 @@ module Dodge.Item.Weapon.Bullet
) where ) where
import Dodge.Data import Dodge.Data
import Dodge.WorldEvent.HitEffect import Dodge.WorldEvent.HitEffect
import Dodge.WorldEvent.SpawnParticle
import Dodge.Particle.Bullet.HitEffect import Dodge.Particle.Bullet.HitEffect
import Geometry.Data import Geometry.Data
@@ -27,6 +29,14 @@ incBullet = BulletAmmo
, _amBulVel = V2 50 0 , _amBulVel = V2 50 0
, _amBulTraj = BasicBulletTrajectory , _amBulTraj = BasicBulletTrajectory
} }
staticBullet :: AmmoType
staticBullet = BulletAmmo
{ _amString = "STATIC"
, _amBulEff = bulletDestroySpawn aStaticBall
, _amBulWth = 2
, _amBulVel = V2 50 0
, _amBulTraj = BasicBulletTrajectory
}
conBullet :: AmmoType conBullet :: AmmoType
conBullet = BulletAmmo conBullet = BulletAmmo
{ _amString = "CONCUSSIVE" { _amString = "CONCUSSIVE"
+10 -1
View File
@@ -22,7 +22,8 @@ lockRoomKeyItems :: RandomGen g => [ (Int -> State g (SubCompTree Room) , State
lockRoomKeyItems = lockRoomKeyItems =
-- [(lasCenSensEdge, takeOne [LAUNCHER,LASGUN,SPARKGUN,FLATSHIELD] ) -- [(lasCenSensEdge, takeOne [LAUNCHER,LASGUN,SPARKGUN,FLATSHIELD] )
-- ,(sensorRoomRunPast Electrical, return SPARKGUN ) -- ,(sensorRoomRunPast Electrical, return SPARKGUN )
[(sensorRoomRunPast Flaming, return FLAMESPITTER ) [(sensorRoomRunPast Electrical, return STATICMODULE )
--,(sensorRoomRunPast Flaming, takeOne [FLAMESPITTER, INCENDIARYMODULE] )
-- ,(sensorRoomRunPast Lasering, return LASGUN ) -- ,(sensorRoomRunPast Lasering, return LASGUN )
-- ,(const slowDoorRoomRunPast, return MINIGUN) -- ,(const slowDoorRoomRunPast, return MINIGUN)
-- ,(const longRoomRunPast, takeOne [SNIPERRIFLE,FLATSHIELD]) -- ,(const longRoomRunPast, takeOne [SNIPERRIFLE,FLATSHIELD])
@@ -67,6 +68,14 @@ itemRooms =
[rc [keyCard 0] [rc [keyCard 0]
] ]
) )
, (INCENDIARYMODULE , join $ takeOne
[rc [incendiaryModule]
]
)
, (STATICMODULE , join $ takeOne
[rc [makeModule STATICMODULE]
]
)
] ]
where where
rc its = do rc its = do
+69 -20
View File
@@ -16,12 +16,17 @@ import LensHelp
import System.Random import System.Random
import Control.Monad.State import Control.Monad.State
-- | Basic bullet hit creature effect.
bulHitCr :: Particle -> Point2 -> Creature -> World -> World crArmourSplit
bulHitCr bt p cr w :: (Particle -> Point2 -> Creature -> World -> World) -- | normal effect
| crIsArmouredFrom p cr -> (Particle -> Point2 -> Creature -> World -> World) -- | armoured effect
= colSpark 8 (_ptColor bt) p1 (argV (p1 -.- p)) $ addDamageArmoured w -> Particle -> Point2 -> Creature -> World -> World
| otherwise = addDamage . bulletHitSound p $ w crArmourSplit normalf armourf bt p cr
| crIsArmouredFrom p cr = armourf bt p cr
| otherwise = normalf bt p cr
basicHitCr :: Particle -> Point2 -> Creature -> World -> World
basicHitCr bt p cr = addDamage . bulletHitSound p
where where
sp = head $ _ptTrail bt sp = head $ _ptTrail bt
bulVel = _ptVel bt bulVel = _ptVel bt
@@ -29,24 +34,27 @@ bulHitCr bt p cr w
mvDams = [ Damage PushDam 1 sp p ep . PushBackDamage $ 2 *.* bulVel ] mvDams = [ Damage PushDam 1 sp p ep . PushBackDamage $ 2 *.* bulVel ]
addDamage = creatures . ix cid . crState . crDamage .++~ addDamage = creatures . ix cid . crState . crDamage .++~
(Damage Piercing 100 sp p ep NoDamageEffect : mvDams) (Damage Piercing 100 sp p ep NoDamageEffect : mvDams)
cid = _crID cr
-- | Basic bullet hit creature effect.
basicHitArmour :: Particle -> Point2 -> Creature -> World -> World
basicHitArmour bt p cr w = colSpark 8 (_ptColor bt) p1 (argV (p1 -.- p)) $ addDamageArmoured w
where
sp = head $ _ptTrail bt
bulVel = _ptVel bt
ep = sp +.+ bulVel
mvDams = [ Damage PushDam 1 sp p ep . PushBackDamage $ 2 *.* bulVel ]
addDamageArmoured = creatures . ix cid . crState . crDamage .++~ mvDams addDamageArmoured = creatures . ix cid . crState . crDamage .++~ mvDams
cid = _crID cr cid = _crID cr
p1 = p +.+ 2 *.* squashNormalizeV (p -.- _crPos cr) p1 = p +.+ 2 *.* squashNormalizeV (p -.- _crPos cr)
bulletHitSound :: Point2 -> World -> World bounceArmour :: Particle -> Point2 -> Creature -> World -> World
bulletHitSound p = soundMultiFrom [CrHitSound 0] p hit1S (Just 10) bounceArmour bt p cr = addBouncer . addDamageArmoured
{- | Bounce off armoured creatures, otherwise do damage. -}
bulBounceArmCr' :: Particle -> Point2 -> Creature -> World -> World
bulBounceArmCr' bt p cr w
| crIsArmouredFrom p cr = addBouncer . addDamageArmoured $ w
| otherwise = addDamage . bulletHitSound p $ w
where where
sp = head $ _ptTrail bt sp = head $ _ptTrail bt
bulVel = _ptVel bt bulVel = _ptVel bt
ep = sp +.+ bulVel ep = sp +.+ bulVel
mvDams = [ Damage PushDam 1 sp p ep . PushBackDamage $ 2 *.* bulVel ] mvDams = [ Damage PushDam 1 sp p ep . PushBackDamage $ 2 *.* bulVel ]
addDamage = creatures . ix cid . crState . crDamage .++~
(Damage Piercing 100 sp p ep NoDamageEffect : mvDams)
addDamageArmoured = creatures . ix cid . crState . crDamage .++~ mvDams addDamageArmoured = creatures . ix cid . crState . crDamage .++~ mvDams
cid = _crID cr cid = _crID cr
newDir = squashNormalizeV (p -.- _crPos cr) newDir = squashNormalizeV (p -.- _crPos cr)
@@ -56,6 +64,16 @@ bulBounceArmCr' bt p cr w
bouncer = (aBulAt Nothing id (Just (_ptColor bt)) Nothing pOut reflectVel (_btDrag bt) bouncer = (aBulAt Nothing id (Just (_ptColor bt)) Nothing pOut reflectVel (_btDrag bt)
(_ptHitEff bt) (_ptWidth bt) (_ptHitEff bt) (_ptWidth bt)
) {_ptTimer = _ptTimer bt - 1} ) {_ptTimer = _ptTimer bt - 1}
-- | Basic bullet hit creature effect.
bulHitCr :: Particle -> Point2 -> Creature -> World -> World
bulHitCr = crArmourSplit basicHitCr basicHitArmour
bulletHitSound :: Point2 -> World -> World
bulletHitSound p = soundMultiFrom [CrHitSound 0] p hit1S (Just 10)
{- | Bounce off armoured creatures, otherwise do damage. -}
bulBounceArmCr :: Particle -> Point2 -> Creature -> World -> World
bulBounceArmCr = crArmourSplit basicHitCr bounceArmour
{- | Bullet pass through creatures. -} {- | Bullet pass through creatures. -}
bulPenCr' :: Particle -> Point2 -> Creature -> World -> World bulPenCr' :: Particle -> Point2 -> Creature -> World -> World
bulPenCr' bt p cr w = w bulPenCr' bt p cr w = w
@@ -77,11 +95,6 @@ hvBulHitCr :: Particle -> Point2 -> Creature -> World -> World
hvBulHitCr bt p cr w = w hvBulHitCr bt p cr w = w
& creatures . ix cid . crState . crDamage .++~ & creatures . ix cid . crState . crDamage .++~
damageGamut 200 100 d1 sp p ep damageGamut 200 100 d1 sp p ep
-- [Piercing 200 sp p ep
-- ,Blunt 100 sp p ep
-- ,TorqueDam 1 d1
-- ,PushDam 1 $ 3 *.* (ep -.- sp)
-- ]
& bulletHitSound ep & bulletHitSound ep
where where
(d1,_) = randomR (-0.7,0.7) $ _randGen w (d1,_) = randomR (-0.7,0.7) $ _randGen w
@@ -97,6 +110,42 @@ damageGamut pdam bdam tor sp p ep =
,Damage PushDam 1 sp p ep . PushBackDamage $ 3 *.* (ep -.- sp) ,Damage PushDam 1 sp p ep . PushBackDamage $ 3 *.* (ep -.- sp)
] ]
bulletDestroySpawn :: (Point2 -> Particle)
-> Particle
-> [(Point2, Either Creature Wall)]
-> World
-> (World, Maybe Particle)
bulletDestroySpawn f = destroyOnImpact (bulDestroyCr f) (bulDestroyWall f)
{- | Create a flamelet when hitting a creature. -}
bulDestroyCr :: (Point2 -> Particle) -> Particle -> Point2 -> Creature -> World -> World
bulDestroyCr f bt p cr w = w
& instantParticles .:~ (f v & ptPos .~ p)
& bulletHitSound p
& creatures . ix cid . crState . crDamage .:~ Damage Piercing 60 sp p ep NoDamageEffect
where
cid = _crID cr
sp = head $ _ptTrail bt
ep = sp +.+ _ptVel bt
v = evalState (randInCirc 1) $ _randGen w
{- | Create flamelet on wall. -}
bulDestroyWall
:: (Point2 -> Particle)
-> Particle
-> Point2 -- Impact point
-> Wall
-> World
-> World
bulDestroyWall f bt p wl = damageWall (Damage Blunt 50 sp p ep NoDamageEffect) wl
. (instantParticles .:~ (f reflectVel & ptPos .~ pOut))
where
ep = sp +.+ _ptVel bt
sp = head $ _ptTrail bt
pOut = p +.+ squashNormalizeV (sp -.- p)
wallV = uncurry (-.-) (_wlLine wl)
reflectVel = squashNormalizeV $ reflectIn wallV (_ptVel bt)
{- | Create a flamelet when hitting a creature. -} {- | Create a flamelet when hitting a creature. -}
bulIncCr :: Particle -> Point2 -> Creature -> World -> World bulIncCr :: Particle -> Point2 -> Creature -> World -> World
bulIncCr bt p cr w = w bulIncCr bt p cr w = w
+5 -4
View File
@@ -13,7 +13,7 @@ module Dodge.WorldEvent.Flash
, laserGunFlashAt , laserGunFlashAt
, teslaGunFlashAt , teslaGunFlashAt
, muzFlareAt , muzFlareAt
, flameFlicker , ptFlicker
) where ) where
import Dodge.Data import Dodge.Data
import Dodge.LightSource import Dodge.LightSource
@@ -61,8 +61,9 @@ explosionFlashAt p = tempLightSources .:~ tlsTimeRadFunPos 20 150 intensityFunc
| x < 10 = 1 / (10 - fromIntegral x) | x < 10 = 1 / (10 - fromIntegral x)
| otherwise = 1 | otherwise = 1
flameFlicker :: Particle -> World -> World ptFlicker :: Particle -> World -> World
flameFlicker pt ptFlicker pt
| _ptTimer pt `mod` 7 == 0 = tempLightSources | _ptTimer pt `mod` 7 == 0 = tempLightSources
.:~ tlsTimeRadColPos 1 70 (V3 0.5 0 0) (addZ 10 $ _ptPos pt) .:~ tlsTimeRadColPos 1 70 (0.5 *.*.* xyzV4 (_ptColor pt)) (addZ 10 $ _ptPos pt)
| otherwise = id | otherwise = id
+89 -31
View File
@@ -3,6 +3,8 @@ module Dodge.WorldEvent.SpawnParticle
( makeGasCloud ( makeGasCloud
, aFlameParticle , aFlameParticle
, makeFlamelet , makeFlamelet
, aStaticBall
, makeStaticBall
) where ) where
import Dodge.Data import Dodge.Data
import Dodge.Base import Dodge.Base
@@ -37,7 +39,7 @@ aFlameParticle t pos vel maycid = PtZ
, _ptCrIgnore = maycid , _ptCrIgnore = maycid
, _ptWidth = 4 , _ptWidth = 4
, _ptTimer = t , _ptTimer = t
, _ptHitEff = doFlameDamCrWall , _ptHitEff = damageBothTypeAmount Flaming 20
, _ptZ = 20 , _ptZ = 20
} }
drawFlame drawFlame
@@ -80,7 +82,7 @@ moveFlame rotd w pt
| otherwise = case thingsHitExceptCr (_ptCrIgnore pt) sp ep w of | otherwise = case thingsHitExceptCr (_ptCrIgnore pt) sp ep w of
((_,Left _):_) -> (doSound $ damwls damcrs , mvPt 0.7) ((_,Left _):_) -> (doSound $ damwls damcrs , mvPt 0.7)
((p,Right wl):_) -> (doSound $ damwls damcrs , rfl wl p) ((p,Right wl):_) -> (doSound $ damwls damcrs , rfl wl p)
_ -> (flameFlicker pt . damwls $ doSound damcrs , mvPt 0.98) _ -> (ptFlicker pt . damwls $ doSound damcrs , mvPt 0.98)
where where
time = _ptTimer pt time = _ptTimer pt
doSound = soundContinue Flame (V2 x y) fireLoudS (Just 2) doSound = soundContinue Flame (V2 x y) fireLoudS (Just 2)
@@ -109,6 +111,44 @@ moveFlame rotd w pt
reflV wall = (0.3 *.* reflectIn (uncurry (-.-) . swap $ _wlLine wall) vel ) reflV wall = (0.3 *.* reflectIn (uncurry (-.-) . swap $ _wlLine wall) vel )
+.+ (0.2 *.* vel) +.+ (0.2 *.* vel)
aStaticBall :: Point2 -> Particle
aStaticBall vel = PtZ
{ _ptDraw = drawStaticBall
, _ptUpdate = moveStaticBall
, _ptVel = vel
, _ptColor = blue
, _ptPos = 0
, _ptCrIgnore = Nothing
, _ptWidth = 3
, _ptTimer = 20
, _ptHitEff = damageBothTypeAmount Electrical 20
, _ptZ = 20
}
makeStaticBall
:: Point2 -- ^ Position
-> Float -- ^ z position
-> Point2 -- ^ Velocity
-> Maybe Int -- ^ Ignore creature id
-> Float -- ^ Size
-> Int -- ^ Timer
-> World
-> World
makeStaticBall (V2 x y) z vel maycid size time w = w
& instantParticles .:~ PtZ
{ _ptDraw = drawStaticBall
, _ptUpdate = moveStaticBall
, _ptVel = vel
, _ptColor = blue
, _ptPos = V2 x y
, _ptCrIgnore = maycid
, _ptWidth = size
, _ptTimer = time
, _ptHitEff = damageBothTypeAmount Electrical 20
, _ptZ = z
}
makeFlamelet makeFlamelet
:: Point2 -- ^ Position :: Point2 -- ^ Position
-> Float -- ^ z position -> Float -- ^ z position
@@ -121,27 +161,42 @@ makeFlamelet
makeFlamelet (V2 x y) z vel maycid size time w = w makeFlamelet (V2 x y) z vel maycid size time w = w
& randGen .~ g & randGen .~ g
& instantParticles .:~ PtZ & instantParticles .:~ PtZ
{ _ptDraw = drawFlameletZ rot { _ptDraw = drawFlameletZ rot
, _ptUpdate = moveFlamelet , _ptUpdate = moveFlamelet
, _ptVel = vel , _ptVel = vel
, _ptColor = red , _ptColor = red
, _ptPos = V2 x y , _ptPos = V2 x y
, _ptCrIgnore = maycid , _ptCrIgnore = maycid
, _ptWidth = size , _ptWidth = size
, _ptTimer = time , _ptTimer = time
, _ptHitEff = doFlameDamCrWall , _ptHitEff = damageBothTypeAmount Flaming 20
, _ptZ = z , _ptZ = z
} }
where where
(rot ,g) = randomR (0,3) $ _randGen w (rot ,g) = randomR (0,3) $ _randGen w
doFlameDamCrWall :: Particle damageBothTypeAmount :: DamageType
-> Int
-> Particle
-> [(Point2, Either Creature Wall)] -> [(Point2, Either Creature Wall)]
-> World -> World
-> (World, Maybe Particle) -> (World, Maybe Particle)
doFlameDamCrWall = destroyOnImpact damageBothTypeAmount dt amount = destroyOnImpact
(doFlameDam 1) (\pt p cr -> creatures . ix (_crID cr) . crState . crDamage .:~ thedam pt p)
(\_ p -> damageWall (Damage Flaming 19 p p p NoDamageEffect)) (\pt p -> damageWall (thedam pt p))
where
thedam pt p = Damage dt amount sp p ep NoDamageEffect
where
sp = _ptPos pt
ep = sp +.+ _ptVel pt
drawStaticBall
:: Particle
-> Picture
drawStaticBall pt = setLayer 1
. setDepth (_ptZ pt + 20)
. uncurryV translate (_ptPos pt)
$ circleSolidCol (_ptColor pt) (withAlpha 0.5 white) (_ptWidth pt+5)
drawFlameletZ drawFlameletZ
:: Float -- ^ Rotation :: Float -- ^ Rotation
@@ -188,30 +243,33 @@ drawFlameletZ rot pt = pictures
s1 = (*) 2 $ log $ 2 + fromIntegral time / 40 s1 = (*) 2 $ log $ 2 + fromIntegral time / 40
s2 = 0.5 * (sc + s1) s2 = 0.5 * (sc + s1)
moveStaticBall :: World -> Particle -> (World, Maybe Particle)
moveStaticBall = moveFlamelet
{- Update of a flamelet. {- Update of a flamelet.
Applies movement and attaches damage to nearby creatures. -} Applies movement and attaches damage to nearby creatures. -}
-- This should be unified in many ways with moveFlame -- This should be unified in many ways with moveFlame
moveFlamelet :: World -> Particle -> (World, Maybe Particle) moveFlamelet :: World -> Particle -> (World, Maybe Particle)
moveFlamelet w pt moveFlamelet w pt
| _ptTimer pt <= 0 = ( w, Nothing) | _ptTimer pt <= 0 = ( w, Nothing)
| otherwise = (flameFlicker pt $ damwls damcrs, mvPt) | otherwise = (ptFlicker pt $ damageInRadius 5 mvPt w, Just mvPt)
where where
sp = _ptPos pt mvPt = pt
vel = _ptVel pt & ptTimer -~ 1
ep = sp +.+ vel & ptPos .~ _ptPos pt +.+ _ptVel pt
size = _ptWidth pt & ptCrIgnore .~ Nothing
mvPt = Just $ pt & ptTimer -~ 1 & ptVel .*.*~ 0.8
& ptPos .~ ep
& ptCrIgnore .~ Nothing -- | Note damgeInRadius by itself never destroys the particle
& ptVel .~ 0.8 *.* vel damageInRadius :: Float -> Particle -> World -> World
damcrs = w & creatures %~ IM.map damifclose damageInRadius size pt w = damwls damcrs
damwls w' = foldr (\wl -> fst . hiteff [(ep,Right wl)]) w' $ IM.filter closeWls $ wallsNearPoint ep w where
closeWls wl = uncurry circOnSeg (_wlLine wl) ep 5 p = _ptPos pt
damcrs = foldr (\cr -> fst . hiteff [(p,Left cr)]) w $ IM.filter isClose $ _creatures w
damwls w' = foldr (\wl -> fst . hiteff [(p,Right wl)]) w' $ IM.filter closeWls $ wallsNearPoint p w
closeWls wl = uncurry circOnSeg (_wlLine wl) p size
isClose cr = dist p (_crPos cr) < _crRad cr + size
hiteff = _ptHitEff pt pt hiteff = _ptHitEff pt pt
isClose cr = dist ep (_crPos cr) < _crRad cr + size
damifclose cr
| isClose cr = cr & crState . crDamage .:~ Damage Flaming 3 sp ep ep NoDamageEffect
| otherwise = cr
-- | At writing the radius is half the size of the effect area -- | At writing the radius is half the size of the effect area
makeGasCloud makeGasCloud
:: Point2 -- ^ Position :: Point2 -- ^ Position
+4
View File
@@ -161,3 +161,7 @@ xV2 (V2 x _) = x
yV2 :: Point2 -> Float yV2 :: Point2 -> Float
{-# INLINE yV2 #-} {-# INLINE yV2 #-}
yV2 (V2 _ y) = y yV2 (V2 _ y) = y
xyzV4 :: V4 a -> V3 a
{-# INLINE xyzV4 #-}
xyzV4 (V4 x y z _) = V3 x y z
+4
View File
@@ -89,6 +89,10 @@ addZ :: Float -> Point2 -> Point3
{-# INLINE addZ #-} {-# INLINE addZ #-}
addZ z (V2 x y) = V3 x y z addZ z (V2 x y) = V3 x y z
v2z :: Point2 -> Float -> Point3
{-# INLINE v2z #-}
v2z (V2 x y) z = V3 x y z
stripZ :: Point3 -> Point2 stripZ :: Point3 -> Point2
{-# INLINE stripZ #-} {-# INLINE stripZ #-}
stripZ (V3 x y _) = V2 x y stripZ (V3 x y _) = V2 x y