Skip to content

Commit

Permalink
✨ Allow bands to self-produce albums
Browse files Browse the repository at this point in the history
  • Loading branch information
sleepyfran committed Apr 4, 2024
1 parent a5221d2 commit b1fab80
Show file tree
Hide file tree
Showing 21 changed files with 305 additions and 68 deletions.
2 changes: 1 addition & 1 deletion src/Duets.Cli/Components/Commands/Studio/Common.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module Studio =
/// Shows a prompt that asks the user if they want to release an album and
/// handles the release.
let promptToReleaseAlbum band unreleasedAlbum =
let (UnreleasedAlbum album) = unreleasedAlbum
let album = unreleasedAlbum |> Album.fromUnreleased

let state = State.get ()

Expand Down
53 changes: 45 additions & 8 deletions src/Duets.Cli/Components/Commands/Studio/CreateAlbum.Command.fs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ module CreateAlbumCommand =
showTextPrompt Studio.createRecordName
|> Album.validateName
|> Result.switch
(promptForTrackList studio finishedSongs)
(promptForFirstTrack studio finishedSongs)
(Studio.showAlbumNameError
>> fun _ -> promptForName studio finishedSongs)

and private promptForTrackList
and private promptForFirstTrack
studio
(finishedSongs: Finished<Song> seq)
name
Expand All @@ -32,24 +32,61 @@ module CreateAlbumCommand =
Generic.cancel
(fun (Finished(fs, currentQuality)) ->
Generic.songWithDetails fs.Name currentQuality fs.Length)
|> Option.iter (promptForConfirmation studio finishedSongs name)
|> Option.iter (promptForProducer studio finishedSongs name)

and private promptForConfirmation studio finishedSongs name selectedSong =
and private promptForProducer studio finishedSongs name firstTrack =
[ SelectedProducer.StudioProducer; SelectedProducer.PlayableCharacter ]
|> showChoicePrompt Studio.producerPrompt (fun producer ->
let state = State.get ()

match producer with
| SelectedProducer.PlayableCharacter ->
let character = Queries.Characters.playableCharacter state

let _, skillLevel =
Queries.Skills.characterSkillWithLevel
state
character.Id
SkillId.MusicProduction

Studio.producerPlayableCharacterSelection skillLevel
| SelectedProducer.StudioProducer ->
let studioPlace = Queries.World.currentPlace state

Studio.producerStudioProducerSelection
studio.PricePerSong
studioPlace.Quality)
|> promptForConfirmation studio finishedSongs name firstTrack

and private promptForConfirmation
studio
finishedSongs
name
selectedSong
producer
=
let (Finished(fs, _)) = selectedSong

let confirmed =
Studio.confirmRecordingPrompt fs.Name |> showConfirmationPrompt

if confirmed then
checkBankAndRecordAlbum studio name selectedSong
checkBankAndRecordAlbum studio producer name selectedSong
else
promptForTrackList studio finishedSongs name
promptForFirstTrack studio finishedSongs name

and private checkBankAndRecordAlbum studio albumName selectedSong =
and private checkBankAndRecordAlbum
studio
selectedProducer
albumName
selectedSong
=
let state = State.get ()

let band = Queries.Bands.currentBand state
let result = startAlbum state studio band albumName selectedSong

let result =
startAlbum state studio selectedProducer band albumName selectedSong

match result with
| Ok effects -> recordWithProgressBar albumName effects
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ module EditAlbumNameCommand =
showOptionalChoicePrompt
"Which album do you want to edit?"
Generic.cancel
(fun (UnreleasedAlbum album) -> album.Name)
(fun (unreleasedAlbum: UnreleasedAlbum) ->
unreleasedAlbum.Album.Name)
unreleasedAlbums
|> Option.iter (promptForAlbumName currentBand)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ module ListUnreleasedAlbumsCommand =

let rows =
unreleasedAlbums
|> List.map (fun (UnreleasedAlbum album) ->
|> List.map (fun (unreleasedAlbum: UnreleasedAlbum) ->
let album = unreleasedAlbum |> Album.fromUnreleased

let albumTrackList =
Queries.Albums.trackList (State.get ()) album

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module RecordSongCommand =
showOptionalChoicePrompt
$"""Select which {Styles.highlight "song"} will be added to the album"""
Generic.cancel
(fun (Finished((fs: Song), currentQuality)) ->
(fun (Finished(fs: Song, currentQuality)) ->
Generic.songWithDetails fs.Name currentQuality fs.Length)
finishedSongs
|> Option.iter (promptForAlbum studio unreleasedAlbums finishedSongs)
Expand All @@ -28,7 +28,8 @@ module RecordSongCommand =
showOptionalChoicePrompt
$"Which album do you want to add {fs.Name} to?"
Generic.cancel
(fun (UnreleasedAlbum album) -> album.Name)
(fun (unreleasedAlbum: UnreleasedAlbum) ->
unreleasedAlbum.Album.Name)
unreleasedAlbums
|> Option.iter (
promptForConfirmation studio unreleasedAlbums finishedSongs song
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ module ReleaseAlbumCommand =
showOptionalChoicePrompt
"Which album do you want to release?"
Generic.cancel
(fun (UnreleasedAlbum album) -> album.Name)
(fun (unreleasedAlbum: UnreleasedAlbum) ->
unreleasedAlbum.Album.Name)
unreleasedAlbums
|> Option.iter (Studio.promptToReleaseAlbum currentBand)

Expand Down
1 change: 0 additions & 1 deletion src/Duets.Cli/Scenes/World.fs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,6 @@ let worldScene mode =

let situation = Queries.Situations.current (State.get ())


match mode with
| ShowDescription ->
let currentRoom = State.get () |> Queries.World.currentRoom
Expand Down
9 changes: 9 additions & 0 deletions src/Duets.Cli/Text/Studio.fs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ let createRecordName =
let createTrackListPrompt =
$"""Select which {Styles.highlight "song"} will be on the {Styles.highlight "track-list"} first"""

let producerPrompt =
$"""Who will be in charge of {Styles.highlight "producing, mixing and mastering"} the record?"""

let producerPlayableCharacterSelection skillLevel =
$"""You (Music production skill: {Styles.Level.from skillLevel}, {Styles.success "free"})"""

let producerStudioProducerSelection pricePerSong studioQuality =
$"""Studio's producer (Skill level: {Styles.Level.from studioQuality}, {Styles.money pricePerSong} extra per song)"""

let confirmRecordingPrompt name =
$"""Are you sure you want to record {Styles.song name}? It will be the first song in the album, and it can't be changed"""

Expand Down
16 changes: 9 additions & 7 deletions src/Duets.Entities/Album.fs
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,21 @@ let updateTrackList album (trackList: TrackList) =
Type = recordType trackList |> Result.unwrap }

/// Returns the inner album of an unreleased album.
let fromUnreleased (UnreleasedAlbum album) = album
let fromUnreleased (unreleasedAlbum: UnreleasedAlbum) = unreleasedAlbum.Album

/// Returns the inner album of a released album.
let fromReleased releasedAlbum = releasedAlbum.Album

module Unreleased =
/// Creates an unreleased album given a name and a track list.
let from band name trackList =
from band name trackList |> UnreleasedAlbum
let from band name trackList producer =
{ Album = from band name trackList
SelectedProducer = producer }

/// Modifies the name of the given album validating that it's correct.
let modifyName (UnreleasedAlbum album) name =
UnreleasedAlbum { album with Name = name }
let modifyName (unreleasedAlbum: UnreleasedAlbum) name =
{ unreleasedAlbum with
UnreleasedAlbum.Album.Name = name }

module Released =
/// Updates an already released album with the new amount of streams and
Expand All @@ -82,8 +84,8 @@ module Released =
Hype = hype }

/// Transforms a given unreleased album into its released status.
let fromUnreleased (UnreleasedAlbum album) releaseDate hype =
{ Album = album
let fromUnreleased (unreleasedAlbum: UnreleasedAlbum) releaseDate hype =
{ Album = unreleasedAlbum.Album
ReleaseDate = releaseDate
Streams = 0
Hype = hype
Expand Down
11 changes: 10 additions & 1 deletion src/Duets.Entities/Types/Album.Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,17 @@ module AlbumTypes =
TrackList: TrackListRef
Type: AlbumType }

/// Represents which producer was chosen to take care of the album production,
/// mixing and mastering.
[<RequireQualifiedAccess>]
type SelectedProducer =
| StudioProducer
| PlayableCharacter

/// Defines an album that was recorded but hasn't been released.
type UnreleasedAlbum = UnreleasedAlbum of Album
type UnreleasedAlbum =
{ Album: Album
SelectedProducer: SelectedProducer }

/// Identifies a review source for albums.
type ReviewerId =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ open Duets.Simulation

module Studio =
/// Returns all interactions available in the current studio room.
let internal interactions state studio =
let internal interactions state studio studioQuality =
let currentBand = Queries.Bands.currentBand state

let finishedSongs =
Expand Down
3 changes: 2 additions & 1 deletion src/Duets.Simulation/Queries/Interactions/Interactions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ module Interactions =
| RehearsalSpace _ ->
RehearsalSpace.interactions state cityId currentRoom.RoomType
| Restaurant -> Restaurant.interactions cityId currentRoom.RoomType
| Studio studio -> Studio.interactions state studio
| Studio studio ->
Studio.interactions state studio currentPlace.Quality

placeSpecificInteractions
|> (@) defaultInteractions
Expand Down
2 changes: 1 addition & 1 deletion src/Duets.Simulation/State/Albums.fs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let private applyToReleased bandId op =
Optic.map releasedAlbumsLens op

let addUnreleased (band: Band) unreleasedAlbum =
let (UnreleasedAlbum album) = unreleasedAlbum
let album = unreleasedAlbum |> Album.fromUnreleased
let addUnreleasedAlbum = Map.add album.Id unreleasedAlbum
applyToUnreleased band.Id addUnreleasedAlbum

Expand Down
2 changes: 1 addition & 1 deletion src/Duets.Simulation/State/State.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ let applyEffect state effect =
Albums.addUnreleased band unreleasedAlbum state
|> Albums.markTrackListAsRecorded band unreleasedAlbum
| AlbumUpdated(band, unreleasedAlbum) ->
let (UnreleasedAlbum album) = unreleasedAlbum
let album = unreleasedAlbum |> Album.fromUnreleased

Albums.removeUnreleased band album.Id state
|> Albums.addUnreleased band unreleasedAlbum
Expand Down
86 changes: 68 additions & 18 deletions src/Duets.Simulation/Studio/RecordAlbum.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,61 @@ open Duets.Simulation
open Duets.Simulation.Bank.Operations
open Duets.Simulation.Time

let private productionQualityImprovement state studio =
let private productionQualityImprovement state studio selectedProducer =
let selectedProducerId =
match selectedProducer with
| SelectedProducer.PlayableCharacter ->
let character = Queries.Characters.playableCharacter state
character.Id
| SelectedProducer.StudioProducer -> studio.Producer.Id

Queries.Skills.characterSkillWithLevel
state
studio.Producer.Id
selectedProducerId
SkillId.MusicProduction
|> fun (_, productionLevel) -> (float productionLevel) * 0.2

let private recordSong state studio (finishedSong: Finished<Song>) =
let private recordSong
state
studio
selectedProducer
(finishedSong: Finished<Song>)
=
let (Finished(song, quality)) = finishedSong

(float quality)
|> (+) (productionQualityImprovement state studio)
|> (+) (productionQualityImprovement state studio selectedProducer)
|> int
|> Math.clamp 0 100
|> fun improvedQuality -> Recorded(song, improvedQuality * 1<quality>)

let private generatePaymentForOneSong state studio (band: Band) =
let private generatePaymentForOneSong
state
studio
selectedProducer
(band: Band)
=
let bandAccount = Band band.Id
expense state bandAccount studio.PricePerSong

let private generateEffectsAfterBilling state studio band effects =
let billingResult = generatePaymentForOneSong state studio band
let totalPrice =
match selectedProducer with
| SelectedProducer.PlayableCharacter ->
studio.PricePerSong (* Includes only the recording fee. *)
| SelectedProducer.StudioProducer ->
studio.PricePerSong
* 2m (* Includes the recording fee and the production fee. *)

expense state bandAccount totalPrice

let private generateEffectsAfterBilling
state
studio
selectedProducer
band
effects
=
let billingResult =
generatePaymentForOneSong state studio selectedProducer band

let timeEffects =
StudioInteraction.CreateAlbum(studio, [])
Expand All @@ -43,12 +76,18 @@ let private generateEffectsAfterBilling state studio band effects =
/// and attempts to generate an album from the name and track list, applying the
/// validations of an album. Also checks the band's bank account and generates
/// the payment to the studio.
let startAlbum state studio band albumName initialSong =
let (Recorded(song, quality)) = recordSong state studio initialSong
let startAlbum state studio selectedProducer band albumName initialSong =
let (Recorded(song, quality)) =
recordSong state studio selectedProducer initialSong

let album = Recorded(song.Id, quality) |> Album.from band albumName

[ AlbumStarted(band, UnreleasedAlbum album) ]
|> generateEffectsAfterBilling state studio band
let unreleasedAlbum =
{ Album = album
SelectedProducer = selectedProducer }

[ AlbumStarted(band, unreleasedAlbum) ]
|> generateEffectsAfterBilling state studio selectedProducer band

/// Applies the improvement in quality given by the producer of the studio and
/// attempts to add a song to the given unreleased album. Generates a payment
Expand All @@ -58,15 +97,26 @@ let recordSongForAlbum
state
studio
(band: Band)
(UnreleasedAlbum album)
(unreleasedAlbum: UnreleasedAlbum)
(song: Finished<Song>)
=
let recordedSong = recordSong state studio song
let trackList = Queries.Albums.trackList state album
let recordedSong =
recordSong state studio unreleasedAlbum.SelectedProducer song

let trackList = Queries.Albums.trackList state unreleasedAlbum.Album

let updatedAlbum =
trackList @ [ recordedSong ] |> Album.updateTrackList album
trackList @ [ recordedSong ]
|> Album.updateTrackList unreleasedAlbum.Album

[ AlbumUpdated(band, UnreleasedAlbum updatedAlbum) ]
|> generateEffectsAfterBilling state studio band
[ AlbumUpdated(
band,
{ unreleasedAlbum with
Album = updatedAlbum }
) ]
|> generateEffectsAfterBilling
state
studio
unreleasedAlbum.SelectedProducer
band
|> Result.map (fun effects -> updatedAlbum, effects)
Loading

0 comments on commit b1fab80

Please sign in to comment.