Fixes issues with climbing up and down. Makefile changes.

parent 2dffbd3a
CONFIG_LIB_OPTS=--ghc-option=-Wall
CONFIG_BIN_OPTS=--prefix="${PWD}/roguestar-local" --ghc-option=-Wall
CONFIG_OPTS=--ghc-option=-Wall
warning:
@echo "See README."
......@@ -19,92 +18,23 @@ install-deps:
cabal install hslogger ${OPTS}
clean:
-rm -rf ./roguestar-local
-rm -rf ./roguestar-sdist
${MAKE} clean-libs
${MAKE} clean-bins
clean-libs:
(cd rsagl-math && cabal clean ${OPTS})
(cd rsagl-frp && cabal clean ${OPTS})
(cd rsagl && cabal clean ${OPTS})
clean-bins:
(cd rsagl-demos && cabal clean ${OPTS})
(cd roguestar-engine && cabal clean ${OPTS})
(cd roguestar-gl && cabal clean ${OPTS})
config-libs1:
(cd rsagl-math && cabal configure --user ${CONFIG_LIB_OPTS} ${OPTS})
config-libs2:
(cd rsagl-frp && cabal configure --user ${CONFIG_LIB_OPTS} ${OPTS})
config-libs3:
(cd rsagl && cabal configure --user ${CONFIG_LIB_OPTS} ${OPTS})
config-bins:
mkdir -p ./roguestar-local
(cd rsagl-demos && cabal configure --user ${CONFIG_BIN_OPTS} ${OPTS})
(cd roguestar-engine && cabal configure --user ${CONFIG_BIN_OPTS} ${OPTS})
(cd roguestar-gl && cabal configure --user ${CONFIG_BIN_OPTS} ${OPTS})
build-libs1:
(cd rsagl-math && cabal build ${OPTS})
build-libs2:
(cd rsagl-frp && cabal build ${OPTS})
build-libs3:
(cd rsagl && cabal build ${OPTS})
build-bins:
(cd rsagl-demos && cabal build ${OPTS})
(cd roguestar-engine && cabal build ${OPTS})
(cd roguestar-gl && cabal build ${OPTS})
install-libs:
install:
(cd rsagl-math && cabal install --reinstall ${OPTS})
(cd rsagl-frp && cabal install --reinstall ${OPTS})
(cd rsagl && cabal install --reinstall ${OPTS})
install-bins:
(cd rsagl-demos && cabal install --reinstall ${OPTS})
(cd roguestar-engine && cabal install --reinstall ${OPTS})
(cd roguestar-gl && cabal install --reinstall ${OPTS})
copy-libs1:
(cd rsagl-math && cabal copy ${OPTS} && cabal register ${OPTS})
copy-libs2:
(cd rsagl-frp && cabal copy ${OPTS} && cabal register ${OPTS})
copy-libs3:
(cd rsagl && cabal copy ${OPTS} && cabal register ${OPTS})
copy-bins:
(cd rsagl-demos && cabal copy ${OPTS})
(cd roguestar-engine && cabal copy ${OPTS})
(cd roguestar-gl && cabal copy ${OPTS})
install:
${MAKE} install-libs
${MAKE} install-bins
from-scratch:
${MAKE} clean -e OPTS=""
${MAKE} config-libs1 -e OPTS=""
${MAKE} build-libs1 -e OPTS=""
${MAKE} copy-libs1 -e OPTS=""
${MAKE} config-libs2 -e OPTS=""
${MAKE} build-libs2 -e OPTS=""
${MAKE} copy-libs2 -e OPTS=""
${MAKE} config-libs3 -e OPTS=""
${MAKE} build-libs3 -e OPTS=""
${MAKE} copy-libs3 -e OPTS=""
${MAKE} config-bins -e OPTS=""
${MAKE} build-bins -e OPTS=""
${MAKE} copy-bins -e OPTS=""
dev:
${MAKE} install -e "OPTS=${CONFIG_OPTS}"
sdist:
(cd rsagl-math && cabal check && cabal sdist ${OPTS})
......
name: roguestar-engine
version: 0.5
license: OtherLicense
license-file: LICENSE
author: Christopher Lane Hinson
maintainer: Christopher Lane Hinson <lane@downstairspeople.org>
category: Game
synopsis: Sci-fi roguelike (turn-based, chessboard-tiled, role playing) game
description: Roguestar is a science fiction themed roguelike (turn-based,
chessboard-tiled, role playing) game written in Haskell. This package
provides the core game engine; you'll probably want to also install the
OpenGL client.
.
The git repository is available at <http://www.downstairspeople.org/git/roguestar-engine.git>.
homepage: http://roguestar.downstairspeople.org/
build-depends: base>=4 && <5,
containers>=0.3.0.0 && < 0.3.1,
array>=0.3.0.0 && < 0.3.1,
old-time>=1.0.0.3 && < 1.1,
random>=1.0.0.2 && < 1.1,
mtl>=1.1.0.2 && < 1.2,
MaybeT>=0.1.2 && < 0.2,
MonadRandom>=0.1.4 && < 0.2,
data-memocombinators>=0.4.0 && < 0.5,
stm>=2.1.1.2 && < 2.2,
parallel>=2.2.0.1 && < 2.3,
bytestring>=0.9.1.5 && < 0.10,
PSQueue>=1.1 && < 1.2,
priority-sync>=0.2.1.0 && < 0.3,
hslogger>=1.1.0 && < 1.2
build-type: Simple
tested-with: GHC==6.12.1
executable: roguestar-engine
main-is: Main.hs
hs-source-dirs: src
other-modules: VisibilityData, Stats, FactionData, Behavior, Alignment,
PlaneData, Grids, Perception, PlaneVisibility,
Turns, Plane, CreatureData,
StatsData, Protocol, Character, Tool,
Substances, HierarchicalDatabase, Travel, ToolData,
CharacterData, Creature, Facing, DBPrivate,
RNG, Species, Position, TerrainData, Combat,
Tests, DBData, GridRayCaster, BeginGame,
SpeciesData, TimeCoordinate, DB, AttributeGeneration,
CreatureAttribute, Building, BuildingData, Town
Random, PlayerState, MakeData, DBErrorFlag, Construction,
Make, Activate, Contact, DeviceActivation, WorkCluster,
Planet, PlanetData
ghc-options: -Wall -threaded -fno-warn-type-defaults
ghc-prof-options: -prof -auto-all
name: roguestar-engine
version: 0.5
cabal-version: -any
build-type: Simple
license: OtherLicense
license-file: LICENSE
copyright:
maintainer: Christopher Lane Hinson <lane@downstairspeople.org>
build-depends: hslogger >=1.1.0 && <1.2,
priority-sync >=0.2.1.0 && <0.3, PSQueue >=1.1 && <1.2,
bytestring >=0.9.1.5 && <0.10, parallel >=2.2.0.1 && <2.3,
stm >=2.1.1.2 && <2.2, data-memocombinators >=0.4.0 && <0.5,
MonadRandom >=0.1.4 && <0.2, MaybeT >=0.1.2 && <0.2,
mtl >=1.1.0.2 && <1.2, random >=1.0.0.2 && <1.1,
old-time >=1.0.0.3 && <1.1, array >=0.3.0.0 && <0.3.1,
containers >=0.3.0.0 && <0.3.1, base >=4 && <5
stability:
homepage: http://roguestar.downstairspeople.org/
package-url:
bug-reports:
synopsis: Sci-fi roguelike (turn-based, chessboard-tiled, role playing) game
description: Roguestar is a science fiction themed roguelike (turn-based,
chessboard-tiled, role playing) game written in Haskell. This package
provides the core game engine; you'll probably want to also install the
OpenGL client.
.
The git repository is available at <http://www.downstairspeople.org/git/roguestar-engine.git>.
category: Game
author: Christopher Lane Hinson
tested-with: GHC ==6.12.1
data-files:
data-dir: ""
extra-source-files:
extra-tmp-files:
executable: roguestar-engine
main-is: Main.hs
buildable: True
build-tools:
cpp-options:
cc-options:
ld-options:
pkgconfig-depends:
frameworks:
c-sources:
extensions:
extra-libraries:
extra-lib-dirs:
includes:
install-includes:
include-dirs:
hs-source-dirs: src
other-modules: TravelData VisibilityData Stats FactionData Behavior
Alignment PlaneData Grids Perception PlaneVisibility Turns Plane
CreatureData StatsData Protocol Character Tool Substances
HierarchicalDatabase Travel ToolData CharacterData Creature Facing
DBPrivate RNG Species Position TerrainData Combat Tests DBData
GridRayCaster BeginGame SpeciesData TimeCoordinate DB
AttributeGeneration CreatureAttribute Building BuildingData Town
Random PlayerState MakeData DBErrorFlag Construction Make Activate
Contact DeviceActivation WorkCluster Planet PlanetData
ghc-prof-options: -prof -auto-all
ghc-shared-options: -prof -auto-all
ghc-options: -Wall -threaded -fno-warn-type-defaults
hugs-options:
nhc98-options:
jhc-options:
\ No newline at end of file
......@@ -15,6 +15,7 @@ import Control.Monad.Error
import Combat
import Activate
import Travel
import TravelData
import Creature
import CreatureData
import Plane
......@@ -80,17 +81,17 @@ dbBehave (Step face) creature_ref =
() | otherwise -> move2ActionTime creature_ref
dbBehave StepDown creature_ref =
do (move_from,move_to) <- dbMove stepDown creature_ref
when (move_from /= move_to) $
dbAdvanceTime creature_ref =<< fullActionTime creature_ref
do _ <- atomic executeClimb $ resolveClimb creature_ref ClimbDown
-- FIXME: should be conditional
dbAdvanceTime creature_ref =<< fullActionTime creature_ref
dbBehave StepUp creature_ref =
do (move_from,move_to) <- dbMove stepUp creature_ref
when (move_from /= move_to) $
dbAdvanceTime creature_ref =<< fullActionTime creature_ref
do _ <- atomic executeClimb $ resolveClimb creature_ref ClimbUp
-- FIXME: should be conditional
dbAdvanceTime creature_ref =<< fullActionTime creature_ref
dbBehave (Jump face) creature_ref =
do atomic $ liftM executeTeleportJump $ resolveTeleportJump creature_ref face
do _ <- atomic executeTeleportJump $ resolveTeleportJump creature_ref face
dbAdvanceTime creature_ref =<< fullActionTime creature_ref
dbBehave (TurnInPlace face) creature_ref =
......@@ -126,14 +127,14 @@ dbBehave (Drop tool_ref) creature_ref =
dbBehave (Fire face) creature_ref =
do _ <- dbMove (turnCreature face) creature_ref
ranged_attack_model <- rangedAttackModel creature_ref
atomic $ liftM executeAttack $ resolveAttack ranged_attack_model face
_ <- atomic executeAttack $ resolveAttack ranged_attack_model face
dbAdvanceTime creature_ref =<< quickActionTime creature_ref
return ()
dbBehave (Attack face) creature_ref =
do _ <- dbMove (turnCreature face) creature_ref
melee_attack_model <- meleeAttackModel creature_ref
atomic $ liftM executeAttack $ resolveAttack melee_attack_model face
_ <- atomic executeAttack $ resolveAttack melee_attack_model face
dbAdvanceTime creature_ref =<< move1ActionTime creature_ref
return ()
......@@ -152,12 +153,12 @@ dbBehave Vanish creature_ref =
return ()
dbBehave Activate creature_ref =
do atomic $ liftM executeActivation $ resolveActivation creature_ref
do _ <- atomic executeActivation $ resolveActivation creature_ref
dbAdvanceTime creature_ref =<< quickActionTime creature_ref
return ()
dbBehave (Make make_prep) creature_ref =
do atomic $ liftM executeMake $ resolveMake creature_ref make_prep
do _ <- atomic executeMake $ resolveMake creature_ref make_prep
dbAdvanceTime creature_ref =<< fullActionTime creature_ref
return ()
......
......@@ -196,15 +196,16 @@ sortByRO f xs =
do y <- f x
return (x,y)
atomic :: (forall m. DBReadable m => m (DB a)) -> DB a
atomic transaction =
do db_a <- ro transaction
(a,s) <- dbSimulate $
do a <- db_a
-- | Run action synthesized from a read-only action (prepare-execute pattern).
atomic :: (x -> DB ()) -> (forall m. DBReadable m => m x) -> DB x
atomic action ro_action =
do x <- ro ro_action
s <- dbSimulate $
do action x
s <- get
return (a,s)
return s
put s
return a
return x
-- |
-- Generates an initial DB state.
......
......@@ -11,14 +11,15 @@ module PlayerState
import DBData
import CreatureData
import MakeData
import TravelData
data PlayerState =
data PlayerState =
RaceSelectionState
| ClassSelectionState Creature
| PlayerCreatureTurn CreatureRef CreatureTurnMode
| SnapshotEvent SnapshotEvent
| GameOver
deriving (Read,Show)
deriving (Read,Show)
data CreatureTurnMode =
NormalMode
......@@ -34,14 +35,14 @@ data CreatureTurnMode =
| ClearTerrainMode
deriving (Read,Show)
data SnapshotEvent =
data SnapshotEvent =
AttackEvent {
attack_event_source_creature :: CreatureRef,
attack_event_source_weapon :: Maybe ToolRef,
attack_event_target_creature :: CreatureRef }
| MissEvent {
miss_event_creature :: CreatureRef,
miss_event_weapon :: Maybe ToolRef }
miss_event_weapon :: Maybe ToolRef }
| KilledEvent {
killed_event_creature :: CreatureRef }
| WeaponOverheatsEvent {
......@@ -61,6 +62,9 @@ data SnapshotEvent =
sunder_event_target_tool :: ToolRef }
| TeleportEvent {
teleport_event_creature :: CreatureRef }
| ClimbEvent {
climb_event_direction :: ClimbDirection,
climb_event_creature :: CreatureRef }
| HealEvent {
heal_event_creature :: CreatureRef }
| ExpendToolEvent {
......@@ -69,7 +73,7 @@ data SnapshotEvent =
-- | Get the 'Creature' acting in the given 'PlayerState'.
creatureOf :: PlayerState -> Maybe CreatureRef
creatureOf state = case state of
creatureOf state = case state of
PlayerCreatureTurn creature_ref _ -> Just creature_ref
SnapshotEvent event -> subjectOf event
GameOver -> Nothing
......@@ -88,9 +92,10 @@ subjectOf event = case event of
SunderEvent { sunder_event_source_creature = attacker_ref } -> Just attacker_ref
TeleportEvent { teleport_event_creature = creature_ref } -> Just creature_ref
HealEvent { heal_event_creature = creature_ref } -> Just creature_ref
ClimbEvent { climb_event_creature = creature_ref } -> Just creature_ref
ExpendToolEvent {} -> Nothing
-- | Current index into the menu, if there is one.
-- | Current index into the menu, if there is one.
menuIndex :: PlayerState -> Maybe Integer
menuIndex state = fst $ modifyMenuIndex_ id state
......
......@@ -239,6 +239,7 @@ dbDispatchQuery ["state"] =
SnapshotEvent (SunderEvent {}) -> "answer: state sunder-event"
SnapshotEvent (TeleportEvent {}) -> "answer: state teleport-event"
SnapshotEvent (HealEvent {}) -> "answer: state heal-event"
SnapshotEvent (ClimbEvent {}) -> "answer: state climb-event"
SnapshotEvent (ExpendToolEvent {}) -> "answer: state expend-tool-event"
GameOver -> "answer: state game-over"
......
......@@ -3,8 +3,9 @@
module Travel
(stepCreature,
turnCreature,
stepDown,
stepUp,
ClimbOutcome,
resolveClimb,
executeClimb,
TeleportJumpOutcome,
resolveTeleportJump,
executeTeleportJump)
......@@ -24,6 +25,7 @@ import Data.List (minimumBy)
import Creature
import CreatureData
import Logging
import TravelData
walkCreature :: (DBReadable db) => Facing ->
(Integer,Integer) ->
......@@ -52,39 +54,45 @@ turnCreature face = walkCreature face (0,0)
-- Travel between planes.
--------------------------------------------------------------------------------
stepDown :: (DBReadable db) => Location CreatureRef () ->
db (Location CreatureRef ())
stepDown l = liftM (fromMaybe l) $ runMaybeT $
do ((p,pos) :: (PlaneRef,Position)) <- MaybeT $ return $ extractParent l
terrain_type <- lift $ terrainAt p pos
when (terrain_type /= Downstairs) $
do lift $ logDB log_travel WARNING $ "Not standing on downstairs."
fail ""
lift $ logDB log_travel DEBUG $ "Stepping down from: " ++ show (p,pos)
let face = fromMaybe Here $ extractParent l
p' <- MaybeT $ getBeneath p
lift $ logDB log_travel DEBUG $ "Stepping down to: " ++ show p'
pos' <- lift $ pickRandomClearSite 10 0 0 pos (== Upstairs) p'
return $ generalizeParent $ toStanding
(Standing { standing_plane = p',
standing_position = pos',
standing_facing = face }) l
data ClimbOutcome =
ClimbGood ClimbDirection CreatureRef Standing
| ClimbFailed
stepUp :: (DBReadable db) => Location CreatureRef () ->
db (Location CreatureRef ())
stepUp l = liftM (fromMaybe l) $ runMaybeT $
do ((p,pos) :: (PlaneRef,Position)) <- MaybeT $ return $ extractParent l
terrain_type <- lift $ terrainAt p pos
when (terrain_type /= Upstairs) $
do lift $ logDB log_travel WARNING $ "Not standing on upstairs."
fail ""
let face = fromMaybe Here $ extractParent l
(p' :: PlaneRef) <- MaybeT $ liftM extractParent $ dbWhere p
pos' <- lift $ pickRandomClearSite 10 0 0 pos (== Downstairs) p'
return $ generalizeParent $ toStanding
(Standing { standing_plane = p',
standing_position = pos',
standing_facing = face }) l
-- |
-- Climb up or down between Planes.
--
resolveClimb :: (DBReadable db) => CreatureRef ->
ClimbDirection ->
db ClimbOutcome
resolveClimb creature_ref direction = liftM (fromMaybe ClimbFailed) $ runMaybeT $
do l <- lift $ dbWhere creature_ref
((p,pos) :: (PlaneRef,Position)) <- MaybeT $ return $ extractParent l
terrain_type <- lift $ terrainAt p pos
let (expected_starting_terrain, expected_landing_terrain) = case direction of
ClimbUp -> (Upstairs,Downstairs)
ClimbDown -> (Downstairs,Upstairs)
when (terrain_type /= expected_starting_terrain) $
do lift $ logDB log_travel WARNING $ "Not standing on correct stairway."
fail ""
lift $ logDB log_travel DEBUG $ "Stepping " ++ show direction ++ " from: " ++ show (p,pos)
let face = fromMaybe Here $ extractParent l
p' <- MaybeT $ case direction of
ClimbDown -> getBeneath p
ClimbUp -> liftM extractParent $ dbWhere p
lift $ logDB log_travel DEBUG $ "Stepping " ++ show direction ++ " to: " ++ show p'
pos' <- lift $ pickRandomClearSite 10 0 0 pos (== expected_landing_terrain) p'
return $ ClimbGood direction creature_ref $
Standing { standing_plane = p',
standing_position = pos',
standing_facing = face }
-- | Execute a resolved climb attempt.
executeClimb :: ClimbOutcome -> DB ()
executeClimb ClimbFailed = return ()
executeClimb (ClimbGood direction creature_ref standing_location) =
do _ <- dbMove (return . toStanding standing_location) creature_ref
dbPushSnapshot $ ClimbEvent direction creature_ref
return ()
--------------------------------------------------------------------------------
-- Teleportation/Jumping
......@@ -120,7 +128,7 @@ resolveTeleportJump creature_ref face = liftM (fromMaybe TeleportJumpFailed) $ r
-- | Execute a resolved teleport jump.
executeTeleportJump :: TeleportJumpOutcome -> DB ()
executeTeleportJump TeleportJumpFailed = return ()
executeTeleportJump (TeleportJumpGood creature_ref standing_location) =
executeTeleportJump (TeleportJumpGood creature_ref standing_location) =
do _ <- dbMove (return . toStanding standing_location) creature_ref
dbPushSnapshot $ TeleportEvent creature_ref
return ()
......
......@@ -76,8 +76,8 @@ dbPerform1PlanarAITurn plane_ref =
dbAdvanceTime plane_ref (1%planar_turn_frequency)
dbPerform1CreatureAITurn :: CreatureRef -> DB ()
dbPerform1CreatureAITurn creature_ref =
atomic $ liftM (flip dbBehave creature_ref) $ P.runPerception creature_ref $ liftM (fromMaybe Vanish) $ runMaybeT $
dbPerform1CreatureAITurn creature_ref = liftM (const ()) $ atomic (flip dbBehave creature_ref) $
P.runPerception creature_ref $ liftM (fromMaybe Vanish) $ runMaybeT $
do player <- MaybeT $ liftM listToMaybe $ filterM (liftM (== Player) . P.getCreatureFaction . child) =<< P.visibleObjects (return . const True)
(rand_x :: Integer) <- lift $ getRandomR (1,100)
rand_face <- lift $ pickM [minBound..maxBound]
......
......@@ -22,7 +22,18 @@ Flag GLUT
Description: Enable GLUT widget toolkit.
Flag GTK
Description: Enable GTL widget toolkit.
Description: Enable GTK widget toolkit.
Executable roguestar
main-is: Main.hs
build-depends: process, old-time, bytestring
ghc-options: -threaded
if flag(GLUT)
cpp-options: -D__ROGUESTAR_SUPPORTS_GLUT__
if flag(GTK)
cpp-options: -D__ROGUESTAR_SUPPORTS_GTK__
if !flag(GLUT) && !flag(GTK)
buildable: False
Executable roguestar-gl
main-is: Main.hs
......@@ -58,7 +69,6 @@ Executable roguestar-gl
vector>=0.6.0.1 && < 0.7,
stm>=2.1.1.2 && < 2.2,
priority-sync>=0.2.1.0 && < 0.2.2
if flag(GLUT)
cpp-options: -D__ROGUESTAR_SUPPORTS_GLUT__
build-depends: GLUT>=2.2.2.0 && < 2.3
......@@ -71,14 +81,3 @@ Executable roguestar-gl
ghc-options: -threaded -fno-warn-type-defaults -fexcess-precision
ghc-prof-options: -prof -auto-all
Executable roguestar
main-is: Main.hs
build-depends: process, old-time, bytestring
ghc-options: -threaded
if flag(GLUT)
cpp-options: -D__ROGUESTAR_SUPPORTS_GLUT__
if flag(GTK)
cpp-options: -D__ROGUESTAR_SUPPORTS_GTK__
if !flag(GLUT) && !flag(GTK)
buildable: False
......@@ -30,9 +30,9 @@ type MessageHandler e m a b = MaybeArrow (FRP e (EventSwitch m)) a b
-- | Print messages about game events.
eventMessager :: (FRPModel m) => EventHandler e m () ()
eventMessager = proc () ->
do eventStateHeader (not . (`elem` recognized_events)) -< ()
blockContinue -< True
eventMessager = proc () ->
do eventStateHeader (not . (`elem` recognized_events)) -< ()
blockContinue -< True
-- | A handler for messages from a specific event state, such as \"attack-event\".
-- Parameters are:
......
......@@ -149,16 +149,22 @@ centerCoordinates = proc () ->
-- | A list of the states that require the screen to be blanked (made black), interrupting the planar visuals.
blanking_states :: [B.ByteString]
blanking_states = ["teleport-event"]
blanking_states = ["teleport-event","climb-event"]
-- | Display the blanked screen and print any blanking events.
blankingDispatch :: (FRPModel m) => B.ByteString -> FRP e (RSwitch Disabled () () SceneLayerInfo m) () SceneLayerInfo
blankingDispatch "teleport-event" = proc () ->
do mainStateHeader (`elem` blanking_states) -< ()
do mainStateHeader (== "teleport-event") -< ()
clearPrintTextOnce -< ()
printTextOnce -< Just (Event,"Whoosh!")
blockContinue <<< arr ((< 0.5) . toSeconds) <<< threadTime -< ()
returnA -< roguestarSceneLayerInfo mempty basic_camera
blankingDispatch "climb-event" = proc () ->
do mainStateHeader (== "climb-event") -< ()
clearPrintTextOnce -< ()
printTextOnce -< Just (Event,"You climb through a network of underground tunnels . . .")
blockContinue <<< arr ((< 0.5) . toSeconds) <<< threadTime -< ()
returnA -< roguestarSceneLayerInfo mempty basic_camera
blankingDispatch blanking_state = proc () ->
do debugOnce -< Just $ "blankingDispatch: unrecognized blanking_state `" `B.append` blanking_state `B.append` "`"
returnA -< roguestarSceneLayerInfo mempty basic_camera
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment