Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Decstar77 committed Apr 21, 2023
0 parents commit 4827be6
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 0 deletions.
61 changes: 61 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Compiled binaries
*.exe
*.dll
*.so
*.dylib
*.a
*.out
*.swp

# Executables
*_test

# Folders
vendor/
dist/
_build/
pkg/

# Dependency directories
node_modules/
Godeps/_workspace/

# IDE and editor files
.vscode/
.idea/
*.iml

# macOS generated files
.DS_Store

# Other
*.log
*.bak
*.tmp
*.orig
*.rej
*.sw?
*.gz
*.zip
*.tar
*.t?z
*.tgz
*.snappy
*.bin
*.pb.go
*.gen.go

# Test coverage
*.coverprofile
*.cover.out
coverage.txt

# Go modules
go.sum
go.mod
!.gitkeep

# Demo files
demos
*.dem
*.info
21 changes: 21 additions & 0 deletions LICENCE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 Decstar77

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# CS-StoryForge

## Introduction

Welcome to the CS-StoryForge repository! This small program is designed to generate a prompt based on a Counter-Strike: Global Offensive (CSGO) demo file. Once the prompt is generated, you can send it to GPT, which will create an engaging story and summary based on the gameplay.

See [example outputs](./example_outputs) from professional games

Please keep in mind it is currently in its first version (v1.0). As such, it may contain bugs and other issues.

## Installation and Usage

Download the pre-built .exe from the releases section. Upon running the program, a file explorer window will open for you to select your demo file. After selection, the program will generate a .txt file with the prompt. You can then copy this prompt into a language model like GPT.

## Tips

Changing the first sentence of the prompt can have drastic changes on the output. Here are some techniques for better output

1. Add a specific player, e.g., "Write a story about a CSGO match highlighting s1mple."
2. Specify the game time, e.g., "Focus on the end game."
3. Adjust adjectives to change the tone.

## Building
1. Ensure that you have Golang installed on your machine.
2. Clone this repository to your local machine.
3. Navigate to the cloned repository and run the following command to build the binary: `go build`
4. Upon successful compilation, you will see a binary named `CS-StoryForge` in your current directory.

## Future
Some thoughts on how to improve the prompt.
1. Information on locations and player movements to provide greater context and facilitate a deeper understanding of the game.
2. Updates on team/player wealth after each round, hopefully offering insights into their financial status and potential strategies.
3. Eliminate mundane rounds, such as uneventful eco rounds, to maintain the LLM's interest on pivotal moments while also reducing token count.
4. Include screenshots, such as of the mini map or perspectives from key players.

Another approach could be to generate a much more comprehensive and detailed prompt for each round. This could lead to short stories on certain rounds rather than a board overview. Explore the possibility of narrating the events in the style of a specific caster and utilizing AI-generated voices.

## License
This project is released under the MIT License. See [LICENSE](LICENCE) for more information.

## Acknowledgements
A huge thank you to the community for your support, feedback, and contributions!

19 changes: 19 additions & 0 deletions example_outputs/faze-vs-cloud9-03-2018.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
As the fierce sun set over the battlefield, the eyes of the world were set on the imminent clash between titans. The bloodthirsty warriors of FaZe Clan and Cloud9 prepared to lock horns, and the echoes of their footsteps resonated through the arena, leaving behind a solemn silence. This would be no ordinary battle; this was a contest of wit, skill, and willpower that would leave one team standing and the other in the dust.

Round 1

The opening round commenced with a bang as olofmeister struck down Skadoodle with the precision of a master assassin. But vengeance was swift, and Stewie2K retaliated with a lethal headshot, extinguishing the flame that was olofmeister. The dance of death had only begun, as both teams traded blows in a symphony of violence. In the end, the steadfast Counter-Terrorists emerged victorious, with the score standing at CT:1, T:0.

Round 2 - 15

The ensuing rounds unfolded in a whirlwind of gunpowder and fury. Tarik and Skadoodle became avatars of destruction, tearing through the ranks of the enemy like a force of nature. But the mighty warriors of FaZe Clan would not be easily subdued. GuardiaN's unwavering gaze found its mark time and time again, while rain and karrigan struck down their foes with ruthless efficiency. As the smoke cleared, the score stood at CT:8, T:7.

Round 16 - 30

The tide of battle shifted, with the once unyielding Counter-Terrorists now adopting the role of Terrorists. The struggle for dominance raged on, with each side displaying feats of strength and cunning that would make the gods weep with envy. Stewie2K, a whirlwind of destruction, sent countless adversaries to their doom, while NiKo and olofmeister continued to carve a path through the enemy's ranks. As the penultimate round drew to a close, the score teetered on a knife's edge: CT:15, T:15.

Round 31

The final round dawned, and the air was thick with anticipation. A hushed silence fell upon the battlefield as the warriors prepared for their last stand. Autimatic struck first, sending rain to the void with a devastating headshot. The sound of gunfire echoed through the air, and as each warrior fell, the weight of their sacrifice hung heavy on the hearts of those who remained.

In the end, it was Cloud9 that claimed victory in this epic confrontation. The warriors of FaZe Clan, though vanquished, had fought valiantly and would forever be remembered for their courage and skill. As the sun set on the battlefield, the sounds of gunfire were replaced by a solemn silence, the memory of the titanic struggle between FaZe Clan and Cloud9 forever etched into the annals of history.
13 changes: 13 additions & 0 deletions example_outputs/og-vs-navi.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
In the grand arena, under a mesmerizing cascade of lights, two titans of the CS:GO world prepared for battle. OGEsports and Natus Vincere locked eyes from across the stage, the tension thickening as the first round approached. The crowd roared in anticipation, with chants for each team erupting in a cacophony of support.

The first round saw mantuu, the sharpshooter of OGEsports, channeling the spirits of the blind monks as he vanquished Boombl4 with a thundering headshot while blinded by his teammate Aleksib. ISSAA, the stoic marksman, silenced the fearsome s1mple with a single well-placed shot. The round ended in a flurry of action, with mantuu securing victory for his team by eliminating Perfecto.

The second round commenced with the battlefield shaking as Boombl4 unleashed his wrath with a Tec-9, obliterating NBK- and ISSAA. Tensions mounted as the game became a pendulum, swinging between triumph and defeat for each side. Ultimately, Boombl4 stood tall, his FAMAS bellowing a victorious cry, as he brought victory to Natus Vincere.

The battle raged on, a dazzling dance of skill and reflexes, with each player demonstrating their mastery of the art of combat. Electronic, Natus Vincere's silent assassin, struck fear into the hearts of his foes, eliminating NBK- while blinded by flamie. The tides of war turned rapidly, as the scoreboard flickered with the ever-changing numbers, reflecting the fierce competition.

Amid the fray, the gladiators mantuu and s1mple engaged in an epic duel of marksmanship, their rifles barking in unison as they traded lethal blows. Aleksib, OGEsports' fearless leader, led his team through the thick of battle, his M4A4 unleashing devastation upon his opponents.

As the match reached its climax, the fate of both teams balanced on the razor's edge. The stage shook with the intensity of the game, each frag echoing like thunder throughout the arena. In the final round, valde, OGEsports' indomitable warrior, unleashed a flurry of destruction, felling his foes with his AK-47, and painting the battlefield in a storm of fire.

With a final, deafening roar, NBK- toppled electronic, sealing the victory for OGEsports. The crowd exploded into a frenzy of cheers and applause, as the victors reveled in their hard-earned triumph. In the end, the final score stood at CT:9, T:16, immortalizing this fateful match in the annals of CS:GO history.
189 changes: 189 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package main

import (
"fmt"
"log"
"os"
"path/filepath"

dem "github.com/markus-wa/demoinfocs-golang/v3/pkg/demoinfocs"
"github.com/markus-wa/demoinfocs-golang/v3/pkg/demoinfocs/common"
events "github.com/markus-wa/demoinfocs-golang/v3/pkg/demoinfocs/events"
"github.com/sqweek/dialog"
)

type Proompt struct {
text string
}

func AddProomptData(proopt *Proompt, s string) {
proopt.text += s + "\n"
}

func ProomptMatchStartData(proopt *Proompt, gs dem.GameState) {
tTeam := gs.TeamTerrorists()
ctTeam := gs.TeamCounterTerrorists()

tName := tTeam.ClanName()
ctName := ctTeam.ClanName()

if len(tName) == 0 {
tName = "Terrorists"
}

if len(ctName) == 0 {
ctName = "Counter Terrorists"
}

AddProomptData(proopt, fmt.Sprintf("%s vs %s", tName, ctName))

tPlayerProompt := tName + ": "
tPlayers := tTeam.Members()
for _, p := range tPlayers {
tPlayerProompt += p.Name + ", "
}

AddProomptData(proopt, tPlayerProompt)

ctPlayerProompt := ctName + ": "
ctPlayers := ctTeam.Members()
for _, p := range ctPlayers {
ctPlayerProompt += p.Name + ", "
}

AddProomptData(proopt, ctPlayerProompt)
}

func ProomptPlayerKillData(proopt *Proompt, e events.Kill) {
hs := ""
if e.IsHeadshot {
hs = "(HeadShot)"
}

AddProomptData(proopt, fmt.Sprintf("%s killed %s with %s%s", e.Killer.Name, e.Victim.Name, e.Weapon.String(), hs))
}

func ProomptPlayerStats(proopt *Proompt, player *common.Player) {
kills := player.Kills()
deaths := player.Deaths()
assists := player.Assists()
totalDamage := player.TotalDamage()
monies := player.MoneySpentTotal()

AddProomptData(proopt, fmt.Sprintf("%s Kills:%d, Deaths:%d, Assists:%d, TotalDamage:%d, TotalMoneySpent:%d", player.Name, kills, deaths, assists, totalDamage, monies))
}

func WritePromptFile(proopt *Proompt, fileName string) error {
file, err := os.Create(fileName)
if err != nil {
return err
}
defer file.Close()

_, err = file.WriteString(proopt.text)
if err != nil {
return err
}

return nil
}

func IsKnifeRound(gs dem.GameState) bool {
for _, p := range gs.TeamCounterTerrorists().Members() {
w := p.Weapons()
if len(w) == 1 && w[0].Type == common.EqKnife {
return true
}
}

return false
}

func DirectoryExists(dir string) (bool, error) {
info, err := os.Stat(dir)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return info.IsDir(), nil
}

func main() {
//filePath := "demos/rolled-16-0.dem"
//filepath := "demos/og-vs-natus-vincere-m2-mirage.dem"

startDir := "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Counter-Strike Global Offensive\\csgo\\replays"

if exists, err := DirectoryExists(startDir); err == nil && !exists {
startDir = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Counter-Strike Global Offensive\\csgo"
}

filePath, err := dialog.File().
Title("Select a demo file").
Filter("Demo files (*.dem)", "dem").
SetStartDir(startDir).
Load()

if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

f, err := os.Open(filePath)

if err != nil {
log.Panic("failed to open demo file: ", err)
}
defer f.Close()

p := dem.NewParser(f)
defer p.Close()

proompt := Proompt{}

AddProomptData(&proompt, "Write a flamboyant narrative story about the following csgo match:")

roundNum := 1
p.RegisterEventHandler(func(e events.RoundStart) {
if p.GameState().IsWarmupPeriod() == false && IsKnifeRound(p.GameState()) == false {

if roundNum == 1 {
ProomptMatchStartData(&proompt, p.GameState())
}

AddProomptData(&proompt, fmt.Sprintf("Round %d", roundNum))
roundNum += 1
}
})

p.RegisterEventHandler(func(e events.Kill) {
if p.GameState().IsWarmupPeriod() == false && IsKnifeRound(p.GameState()) == false && roundNum > 1 {
ProomptPlayerKillData(&proompt, e)
}
})

p.RegisterEventHandler(func(e events.RoundEnd) {
if p.GameState().IsWarmupPeriod() == false && IsKnifeRound(p.GameState()) == false && roundNum > 1 {
AddProomptData(&proompt, fmt.Sprintf("Round over: %s", e.Message))
}
})

p.RegisterEventHandler(func(e events.AnnouncementWinPanelMatch) {
AddProomptData(&proompt, "======Player stats======")
for _, pl := range p.GameState().Participants().Playing() {
ProomptPlayerStats(&proompt, pl)
}

AddProomptData(&proompt, "======Game over======")
})

err = p.ParseToEnd()
if err != nil {
log.Panic("failed to parse demo: ", err)
}

fileName := filepath.Base(filePath) + "-proompt.txt"
WritePromptFile(&proompt, fileName)
}

0 comments on commit 4827be6

Please sign in to comment.